声明:本文参照——剑指Offer——编程题的Java实现,并对一些算法进行优化,以下简称《参考》。
面试题11:数值的整数次方
题目大致为:
实现函数double power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
实现函数double power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:
可以考虑对指数折半,这样只需要计算一半的值,若指数是奇数,则-1再折半,否则直接折半。
【注意】《参考》中未考虑指数为负数的情况。
Java实现
public class MainTest {
public static void main(String[] args) {
int base = 3;
int exponent = -3;
System.out.println(power(base, exponent));
exponent = 6;
System.out.println(power(base, exponent));
}
public static double power(double base, int exponent) {
if (exponent == 0) return 1;
if (exponent < 0) return 1 / power(base, -exponent);
if (exponent % 2 == 0) {
double temp = power(base, exponent >> 1);
return temp * temp;
} else {
double temp = power(base, (exponent - 1 )>> 1);
return temp * temp * base;
}
}
}
运行结果:
0.037037037037037035
729.0
面试题13:在O(1)时间删除链表结点
题目大致为:
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
思路:
想要在O(1)时间内删除链表的指定结点,要遍历的话得O(n),则肯定不能遍历。若是要删除的结点不是尾结点,那么可以将后面的那个值复制到该指针处,并将后面指针所指空间删除,用复制+删除后面的实现删除,时间复杂度为O(1)。对于尾结点,需要遍历,那么时间复杂度是O(n),但是总的时间复杂度为[(n-1)*O(1)+O(n)]/n,结果是O(1)。
【注意】链表只有一个节点,切要删除该节点(即头结点),是无法办到的,这点在《参考》中未提及。
package cn.learn.test;
public class MainTest {
public static void main(String[] args) {
// 构建链表
ListNode head = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode node_4 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
ListNode node_7 = new ListNode(7);
head.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_4);
node_4.setNext(node_5);
node_5.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
// 输出原始链表
System.out.println("原始链表:");
printList(head);
System.out.println("----------------");
// 删除结点node_3
deleteNode(head, node_3);
System.out.println("删除node_3后链表:");
printList(head);
System.out.println("----------------");
// 删除结点head
deleteNode(head, head);
System.out.println("删除head后链表:");
printList(head);
System.out.println("----------------");
}
public static void deleteNode(ListNode head, ListNode toBeDeleted) {
if (toBeDeleted == null || head == null) return;
if (toBeDeleted.next != null) { //删除的是中间节点
ListNode temp = toBeDeleted.next;
toBeDeleted.value = temp.value;
toBeDeleted.next = temp.next;
}
// 【注意】这部分代码不起作用,故注释了。
// else if (head == toBeDeleted) {
// // 如果头结点就是要删除的节点
// head = null;
// }
else {
ListNode temp = head;
while (temp.next != toBeDeleted) {
temp = temp.next;
}
temp.next = null;
}
}
/**
* 打印链表
*
* @param head头指针
*/
public static void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.getValue() + "、");
current = current.getNext();
}
System.out.println();
}
}
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}运行结果:
原始链表:
1、2、3、4、5、6、7、
----------------
删除node_3后链表:
1、2、4、5、6、7、
----------------
删除head后链表:
2、4、5、6、7、
----------------
面试题14:调整数组顺序使奇数位于偶数前面
题目大致为:
对于一个数组,实现一个函数使得所有奇数位于数组的前半部分,偶数位于数组的后半部分。
思路:
可以使用双指针的方式,一个指针指向数组的开始,一个指针指向数组的尾部,如果头部的数为偶数且尾部的数是奇数则交换,否则头部指针向后移动,尾部指针向前移动,直到两个指针相遇
【注意】这里没有要求调整前后奇数的相对位置和偶数相对位置一致。
Java代码:
public class MainTest {
public static void main(String[] args) {
int arr[] = {1, 2, 3, 4, 5, 6};
rejectArray(arr);
System.out.println(Arrays.toString(arr));
}
public static void rejectArray(int[] arr) {
int l = 0, r = arr.length - 1;
while (l < r) {
if (arr[l] % 2 == 1) {
l++;
} else if (arr[l] % 2 == 0 && arr[r] % 2 == 1) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
l++;
r--;
} else {
r--;
}
}
}
}运行结果:
[1, 5, 3, 4, 2, 6]
面试题15:链表中倒数第k个结点
题目大致为:
在一个链表中,查找倒数的第k个数。
思路:
使用双指针的方式,前一个指针先走k步(中间隔k-1个结点),后一个指针才开始走,直到第一个指针走到尾,后一个指针指向的就是要找的倒数第k个数。值得注意的是:1、k是否超过链表长度且k必须为正整数;2、链表是否为空。
Java代码:
public class MainTest {
public static void main(String[] args) {
// 构建链表
ListNode head = new ListNode(1);
ListNode h1 = new ListNode(2);
ListNode h2 = new ListNode(3);
ListNode h3 = new ListNode(4);
ListNode h4 = new ListNode(5);
ListNode h5 = new ListNode(6);
head.setNext(h1);
h1.setNext(h2);
h2.setNext(h3);
h3.setNext(h4);
h4.setNext(h5);
h5.setNext(null);
// 查找倒数第k个
ListNode p = findKthToTail(head, 3);
System.out.println(p.getValue());
}
public static ListNode findKthToTail(ListNode head, int k) {
if (head == null || k <= 0) {
return null;
}
ListNode prePoint = head;
ListNode postPost = head;
while (k-- > 0) {
prePoint = prePoint.next;
if (prePoint == null) {
return null;
}
}
while (prePoint != null) {
prePoint = prePoint.next;
postPost = postPost.next;
}
return postPost;
}
}
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}运行结果:
4
面试题16:反转链表
题目大致为:
对于一个链表,反转该链表并返回头结点。
思路:
主要是指针的操作,但是要注意不能断链。这里可以使用非递归的方式求解。
Java代码
public class MainTest {
public static void main(String[] args) {
// 构建链表
ListNode head = new ListNode(1);
ListNode h1 = new ListNode(2);
ListNode h2 = new ListNode(3);
ListNode h3 = new ListNode(4);
ListNode h4 = new ListNode(5);
ListNode h5 = new ListNode(6);
head.setNext(h1);
h1.setNext(h2);
h2.setNext(h3);
h3.setNext(h4);
h4.setNext(h5);
h5.setNext(null);
// 查找倒数第k个
ListNode p = reverseList(head);
while (p != null) {
System.out.print(p.value + "、");
p = p.next;
}
}
public static ListNode reverseList(ListNode head) {
ListNode reverseHead = null;
ListNode post = head;
ListNode pre = null;
while (post != null) {
pre = post.next;
post.next = reverseHead;
reverseHead = post;
post = pre;
}
return reverseHead;
}
}
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}运行结果
6、5、4、3、2、1、
面试题17:合并两个排序的链表
题目大致为:
输入两个递增排序的链表,合并这两个链表并使得新链表中的结点仍然按照递增排序的。
思路:
主要是链表中值的比较,取较小的结点插入到新的链表中。
Java代码:
public class MainTest {
public static void main(String[] args) {
// 构建链表1
ListNode head1 = new ListNode(1);
ListNode node1_2 = new ListNode(3);
ListNode node1_3 = new ListNode(5);
ListNode node1_4 = new ListNode(7);
head1.setNext(node1_2);
node1_2.setNext(node1_3);
node1_3.setNext(node1_4);
node1_4.setNext(null);
// 构建链表2
ListNode head2 = new ListNode(2);
ListNode node2_2 = new ListNode(4);
ListNode node2_3 = new ListNode(6);
ListNode node2_4 = new ListNode(8);
ListNode node2_5 = new ListNode(10);
ListNode node2_6 = new ListNode(12);
head2.setNext(node2_2);
node2_2.setNext(node2_3);
node2_3.setNext(node2_4);
node2_4.setNext(node2_5);
node2_5.setNext(node2_6);
System.out.println("链表1:");
printList(head1);
System.out.println("-------------");
System.out.println("链表2:");
printList(head2);
System.out.println("-------------");
System.out.println("合并后的链表:");
ListNode head = mergeList(head1, head2);
printList(head);
System.out.println("-------------");
}
public static ListNode mergeList(ListNode h1, ListNode h2) {
ListNode pHead = new ListNode(-1), p = pHead;
while (h1 != null && h2 != null) {
if (h1.value < h2.value) {
p.next = h1;
h1 = h1.next;
} else {
p.next = h2;
h2 = h2.next;
}
p = p.next;
}
if (h1 == null) p.next = h2;
if (h2 == null) p.next = h1;
return pHead.next;
}
/**
* 打印链表
*
* @param head头指针
*/
public static void printList(ListNode head) {
ListNode current = head;
while (current != null) {
System.out.print(current.getValue() + "、");
current = current.getNext();
}
System.out.println();
}
}
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}运行结果:
链表1:
1、3、5、7、
-------------
链表2:
2、4、6、8、10、12、
-------------
合并后的链表:
1、2、3、4、5、6、7、8、10、12、
-------------
面试题19:二叉树的镜像
题目大致为:
给定一棵二叉树,将其每一个结点的左右子树交换,这就叫做镜像。
思路:
先对其根节点的左右子树处理,交换左右子树,此时再递归处理左右子树。这里要注意分为三种情况:1、树为空;2、只有根结点;3、左右子树至少有一个不为空。
Java代码:
public class MainTest {
public static void main(String[] args) {
// 构建二叉树
TreeNode root = new TreeNode(8);
TreeNode t1 = new TreeNode(6);
TreeNode t2 = new TreeNode(10);
TreeNode t3 = new TreeNode(5);
TreeNode t4 = new TreeNode(7);
TreeNode t5 = new TreeNode(9);
TreeNode t6 = new TreeNode(11);
root.setLeft(t1);
root.setRight(t2);
t1.setLeft(t3);
t1.setRight(t4);
t2.setLeft(t5);
t2.setRight(t6);
t3.setLeft(null);
t3.setRight(null);
t4.setLeft(null);
t4.setRight(null);
t5.setLeft(null);
t5.setRight(null);
t6.setLeft(null);
t6.setRight(null);
printPreOrder(root);
System.out.println();
// 求镜像
mirrorRecursively(root);
// 前序遍历输出
printPreOrder(root);
}
public static void mirrorRecursively(TreeNode root) {
if (root == null) return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
mirrorRecursively(root.left);
mirrorRecursively(root.right);
}
public static void printPreOrder(TreeNode root) {
if (root != null) {
System.out.print(root.value + "、");
printPreOrder(root.left);
printPreOrder(root.right);
}
}
}
class TreeNode {
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
}运行结果:
8、6、5、7、10、9、11、
8、10、11、9、6、7、5、
面试题20:顺时针打印矩阵
面试题21:包含min函数的栈
题目大致为:
定义栈的数据结构,在给类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。
思路:
可以建一个辅助的栈,在插入的过程中,插入栈1,同时在插入辅助栈的过程中要求与栈中的元素比较,若小于栈顶元素,则插入该元素,若大于栈顶元素,则继续插入栈顶元素。
Java实现:
public class MainTest {
public static void main(String[] args) {
StackWithMin s = new StackWithMin();
s.push(3);
s.push(4);
s.push(2);
System.out.println(s.min());
s.push(1);
System.out.println(s.min());
s.pop();
System.out.println(s.min());
s.pop();
System.out.println(s.min());
s.pop();
System.out.println(s.min());
}
}
class StackWithMin {
private Stack<Integer> stack;
private Stack<Integer> stackHelp;// 一个辅助的栈
public StackWithMin() {
stack = new Stack<Integer>();
stackHelp = new Stack<Integer>();
}
/**
* 直接插入stack中,在插入stackHelp时,如果为空则直接插入,或者要判断与顶部元素的大小,若小于则插入,若大于则继续插入顶部元素
*
* @param t
* 待插入元素
*/
public void push(int t) {
stack.push(t);
// 插入辅助的栈
if (stackHelp.size() == 0 || t < stackHelp.peek()) {
stackHelp.push(t);
} else {
stackHelp.push(stackHelp.peek());
}
}
/**
* 出栈,要求stack和stackHelp均不为空
*
* @return
*/
public int pop() {
assert (stack.size() > 0 && stackHelp.size() > 0);
stackHelp.pop();
return stack.pop();
}
/**
* 取得最小值,最小值一定是stackHelp的栈顶元素
*
* @return
*/
public int min() {
assert (stack.size() > 0 && stackHelp.size() > 0);
return stackHelp.peek();
}
}运行结果:
2
1
2
3
3
面试题22:栈的压入、弹出序列
题目大致为:
输入两个整数序列,第一个序列表示栈的压入顺序,判断第二个序列是否为该栈的弹出顺序。
思路:
主要分为这样的几种情况:首先判断两个序列的长度是否相等,若相等且大于0,则利用辅助栈模拟入栈和出栈。如果栈为空,则入栈,此时若栈顶元素与出栈序列的第一个元素相等,则出栈,否则继续入栈,最后判断栈是否为空且出栈序列所有的元素都遍历完。
Java代码:
public class MainTest {
public static void main(String[] args) {
// 测试用例
// 第一组
int pushArray_1[] = { 1, 2, 3, 4, 5 };
int popArray_1[] = { 4, 5, 3, 2, 1 };
System.out.println("第一组:" + isPopOrder(pushArray_1, popArray_1));
// 第二组
int pushArray_2[] = { 1, 2, 3, 4, 5 };
int popArray_2[] = { 4, 3, 5, 1, 2 };
System.out.println("第二组:" + isPopOrder(pushArray_2, popArray_2));
// 第三组,主要长度不等
int pushArray_3[] = { 1, 2, 3, 4, 5 };
int popArray_3[] = { 4, 5, 3 };
System.out.println("第三组:" + isPopOrder(pushArray_3, popArray_3));
}
public static boolean isPopOrder(int[] pushArray, int[] popArray) {
if (pushArray.length != popArray.length) return false;
Stack<Integer> stack = new Stack<>();
int popIndex = 0, pushIndex = 0;
while (pushIndex < pushArray.length) {
if (stack.size() > 0 && stack.peek() == popArray[popIndex]) {
stack.pop();
popIndex++;
} else {
stack.push(pushArray[pushIndex++]);
}
}
while (stack.size() > 0) {
if (stack.pop() != popArray[popIndex++]) {
return false;
}
}
return true;
}
}运行结果:
第一组:true
第二组:false
第三组:false
面试题23:从上往下打印二叉树
题目大致为:
从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。
思路:
可以使用队列的方式存储,先将根结点入队,若队列不为空,则取出队列的头,若这个结点有左孩子,则左孩子入队,若有右孩子,则右孩子入队。
Java代码实现:
public class MainTest {
public static void main(String[] args) {
// 构建二叉树
TreeNode root = new TreeNode(8);
TreeNode t1 = new TreeNode(6);
TreeNode t2 = new TreeNode(10);
TreeNode t3 = new TreeNode(5);
TreeNode t4 = new TreeNode(7);
TreeNode t5 = new TreeNode(9);
TreeNode t6 = new TreeNode(11);
root.setLeft(t1);
root.setRight(t2);
t1.setLeft(t3);
t1.setRight(t4);
t2.setLeft(t5);
t2.setRight(t6);
// 层次遍历
System.out.println("层次遍历序列:");
printFromTopToBottom(root);
}
public static void printFromTopToBottom(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
TreeNode temp = null;
while (!queue.isEmpty()) {
temp = queue.poll();
System.out.print(temp.value + "、");
if (temp.left != null) {
queue.add(temp.left);
}
if (temp.right != null) {
queue.add(temp.right);
}
}
}
}运行结果:
层次遍历序列:
8、6、10、5、7、9、11、
面试题24:二查搜索树的后续遍历序列
题目大致为:
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。
思路:
主要考察的是二叉搜索树和后序遍历的性质,其中后序遍历的最后一个数是根结点,在二叉搜索树中,左子树的值都小于根结点,右子树的值都大于跟结点,这样以数组的最后一个节点root(作为根节点)为界,将数组划分为的前半部分(左子树)均小于root,后半部分(右子树)均大于root,当然前半部分(左子树)或后半部分(右子树)不存在也是可以的(即非平衡二叉搜索树)。以此递归,如果每次都能成功划分出左子树则返回true,否则返回false。
Java代码:
public class MainTest {
public static void main(String[] args) {
int array[] = { 5, 7, 6, 9, 11, 10, 8 }; //true
System.out.println(verifySquenceOfBST(array, 0, array.length - 1));
int[] array1 = {8, 7}; //true
System.out.println(verifySquenceOfBST(array1, 0, array1.length - 1));
int[] array2 = {9, 7, 8}; //false
System.out.println(verifySquenceOfBST(array2, 0, array2.length - 1));
int array3[] = { 5, 7, 6, 19, 11, 10, 8 }; //true
System.out.println(verifySquenceOfBST(array3, 0, array3.length - 1));
}
public static boolean verifySquenceOfBST(int squence[], int l, int r) {
if (l >= r) return true; //为什么l>r的时候也返回true?请结合array4进行分析
int root = squence[r], i = l;
for (; i < r; i++) {
if (squence[i] > root) break;
}
int j = i;
for (; j < r; j++) {
if (squence[j] < root) return false;
}
if (j < r) return false;
return verifySquenceOfBST(squence, l, i - 1) &&
verifySquenceOfBST(squence, i, r - 1);
}
}运行结果:
true
true
false
true
true
面试题29:数组中出现次数超过一半的数字
题目大致为:
数组中有一个数字出现的次数超过数组长度的一般,找出这个数字。
思路:
在遍历数组的过程中纪录两个量,一个是数组中的数字,一个是次数,当下一个数字和我们保存的一致时则次数加1,当不一致时次数减1,当次数为0时,重置两个量。数组中的数字为当前访问的值,次数为1。这里主要是利用了出现的次数超过了一半,其实就是超过一半数出现的次数减去其他的数出现的次数始终是大于0的。
Java实现
public class MainTest {
public static void main(String[] args) {
int testArray[] = { 1, 2, 3, 2, 2, 2, 5, 4, 2 };
System.out.println("超过一半的数字为:" + moreThanHalfNum(testArray));
}
private static int moreThanHalfNum(int[] array) {
int times = 0, num = 0;
for (int i = 0; i < array.length; i++) {
if (times == 0) {
num = array[i];
times++;
} else if (num != array[i]) {
times--;
} else {
times++;
}
}
return num;
}
}运行结果:
超过一半的数字为:2
面试题30:最小的k个数
题目大致为:
输入n个整数,找出其中最小的k个数。
思路:
使用类似二叉查找树的形式实现,控制好树中的结点个数为k。
Java代码:
import java.util.Iterator;
import java.util.TreeSet;
public class MainTest {
public static void main(String[] args) {
// 测试的例子
int array[] = { 4, 5, 1, 6, 2, 7, 3, 8 };
final int k = 4;
TreeSet<Integer> set = getKMinNums(array, k);
// 输出
Iterator<Integer> it = set.iterator();
System.out.println("最小的" + k + "个数为:");
while (it.hasNext()) {
System.out.print(it.next() + "、");
}
}
public static TreeSet<Integer> getKMinNums(int[] arr, int k) {
if (arr == null || k <= 0) {
return null;
}
TreeSet<Integer> set = new TreeSet<>();
for (int i = 0; i < arr.length; i++) {
if (set.size() < k) {
set.add(arr[i]);
} else {
if (set.last() > arr[i]) {
set.pollLast();
set.add(arr[i]);
}
}
}
return set;
}
}运行结果:
最小的4个数为:
1、2、3、4、
面试题31:连续字数组的最大和
题目大致为:
输入一个整型数组,数组里有正数也有负数。数组中一个或者连续的多个整数组成一个字数组。求所有字数组的和的最大值。要求时间复杂度为O(n)。
思路:
本题考查动态规划算法,如何从包含当前节点的最优解得到全局最优解,关键点就是得到状态转移方程。因为时间复杂度为O(n),则只能遍历一次数组,这里同时使用两个变量currentSum和currentMax,其中currentSum保存的是当前连续子序列的和。若currentSum<0,则丢弃之前的子序列,并从下一个位置从新记录,currentMax记录的是历史的最大值,只有当currentSum>curremtMax时用currentSum替换currentSum。注意:原博客中currentMax初始值为0,这样是错误的。以测试数组int[] array={-1}为例,就可以看到结果为-1,而原博客的代码将得到0这样一个错误的结果。
Java代码
public class MainTest {
public static void main(String[] args) {
// 测试
int array[] = { -1, -2, 3, 10, -4, 7, 2, -5 };
int result = findMaxSumOfSubArray(array);
System.out.println("子数组的最大和为:" + result);
}
public static int findMaxSumOfSubArray(int[] array) {
int currentMax = Integer.MIN_VALUE;
int currentSum = 0;
for (int i = 0; i < array.length; i++) {
if (currentSum < 0) {
currentSum = 0;
}
currentSum += array[i];
if (currentMax < currentSum) {
currentMax = currentSum;
}
}
return currentMax;
}
}运行结果:
子数组的最大和为:18
面试题34:丑数
题目大致为:
丑数的定义为:只包含因子2,3和5的数。求按从小到大的顺序的第1500个丑数。约定:1当做第一个丑数。
思路:
设置三个指针分别代表该位置*2,*3和*5,并将这三个数中的最小值插入数组中,若当前位置的值*对应的因子<=刚插入的值,便将该指针后移,直到新的位置上的值*对应的因子>刚插入的值。
Java实现
public class MainTest {
public static void main(String[] args) {
System.out.println("第" + 7 + "个丑数为:" + getUglyNum(7));
System.out.println("第" + 1500 + "个丑数为:" + getUglyNum(1500));
}
public static int getUglyNum(int index) {
int[] temp = new int[index];
temp[0] = 1;
int a = 0, b = 0, c = 0, multi2 = 0, multi3 = 0, multi5 = 0;
for (int i = 1; i < index; i++) {
multi2 = temp[a] << 1;
multi3 = temp[b] * 3;
multi5 = temp[c] * 5;
int min = Math.min(Math.min(multi2, multi3), multi5);
temp[i] = min;
if (min == multi2) a++;
if (min == multi3) b++;
if (min == multi5) c++;
}
return temp[index - 1];
}
}运行结果:
第7个丑数为:8
第1500个丑数为:859963392
面试题35:第一个只出现一次的字符
题目大致为:
在字符串中找出第一个只出现一次的字符。
思路:
一种做法用Hashmap<String, Integer>两次遍历,第一次遍历统计出每个字符出现的次数,第二次遍历将这个只出现一次的字符找出。
另一种做法的基本思路和第一种思路类似,只不过数组short[i]中的i是字母与A的距离,short[i]的值是出现的次数。本题解法采用方法二。
Java实现
public class MainTest {
public static void main(String[] args) {
System.out.println("第一个只出现一次的字符为:" + findFirstAloneCharacter("AabaccdeffdezA"));
}
public static char findFirstAloneCharacter(String str) {
//本题只可以处理字母和少数字符,如果要包含其他更多字符,可以将table的长度加大
short[] table = new short[(short)'z' - 'A' + 1];
for (int i = 0; i < str.length(); i++) {
table[str.charAt(i) - 'A']++;
}
for (int i = 0; i < str.length(); i++) {
if (table[str.charAt(i) - 'A'] == 1) return str.charAt(i);
}
return 0;
}
}运行结果:
第一个只出现一次的字符为:b
面试题37:两个链表的第一个公共结点
题目大致为:
输入两个链表,找出它们的第一个公共结点。
思路:
第一个公共结点开始往后都是公共结点,所以在末尾向前遍历,就可以找到第一个公共结点。利用上面的思想,可以先计算两个链表的长度,计算两个链表的长度差,然后先遍历较长的链表,等到剩余长度相等时开始同时遍历,这样就能较快地找到相同的结点,时间复杂度为O(m+n),其中m,n分别为两个链表的长度。
Java实现
public class MainTest {
public static void main(String[] args) {
// 构建链表
ListNode head1 = new ListNode(1);
ListNode node_2 = new ListNode(2);
ListNode node_3 = new ListNode(3);
ListNode head2 = new ListNode(4);
ListNode node_5 = new ListNode(5);
ListNode node_6 = new ListNode(6);
ListNode node_7 = new ListNode(7);
head1.setNext(node_2);
node_2.setNext(node_3);
node_3.setNext(node_6);
node_6.setNext(node_7);
node_7.setNext(null);
head2.setNext(node_5);
node_5.setNext(node_6);
ListNode result = getFirstIntesection(head1, head2);
System.out.println("第一个公共结点:" + result.getValue());
}
public static ListNode getFirstIntesection(ListNode l1, ListNode l2) {
ListNode p1 = l1, p2 = l2;
int len1 = 0, len2 = 0;
while (p1 != null) {
p1 = p1.next;
len1++;
}
while (p2 != null) {
p2 = p2.next;
len2++;
}
ListNode pLong = l1, pShort = l2;
int gap = len1 - len2;
if (len1 < len2) {
pLong = l2;
pShort = l1;
gap = len2 - len1;
}
while (gap-- > 0) {
pLong = pLong.next;
}
while (pShort != null) {
if (pLong == pShort) return pShort;
pShort = pShort.next;
pLong = pLong.next;
}
return null;
}
}
class ListNode {
int value;
ListNode next;
public ListNode(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ListNode getNext() {
return next;
}
public void setNext(ListNode next) {
this.next = next;
}
}运行结果:
第一个公共结点:6
面试题39:二叉树的深度
题目大致为:
输入一棵二叉树的根结点,求该树的深度。其中,从根结点到叶结点一次经过的结点形成树的一条路径,最长路径的长度为树的深度。
思路:
树的遍历可以利用递归实现,查找深度其实也是遍历的一种,分别遍历左右子树,找到左右子树中结点的个数。
Java代码:
public class MainTest {
public static void main(String[] args) {
TreeNode root = new TreeNode(1);
TreeNode node_2 = new TreeNode(2);
TreeNode node_3 = new TreeNode(3);
TreeNode node_4 = new TreeNode(4);
TreeNode node_5 = new TreeNode(5);
TreeNode node_6 = new TreeNode(6);
TreeNode node_7 = new TreeNode(7);
root.setLeft(node_2);
root.setRight(node_3);
node_2.setLeft(node_4);
node_2.setRight(node_5);
node_3.setRight(node_6);
node_5.setLeft(node_7);
// 计算深度
System.out.println("二叉树的深度为:" + getTreeDepth(root));
}
public static int getTreeDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(getTreeDepth(root.left),
getTreeDepth(root.right)) + 1;
}
}
class TreeNode {
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
}
public TreeNode getLeft() {
return left;
}
public void setLeft(TreeNode left) {
this.left = left;
}
public TreeNode getRight() {
return right;
}
public void setRight(TreeNode right) {
this.right = right;
}
}运行结果:
二叉树的深度为:4
面试题41:和为s的两个数字VS和为s的连续正数序列
题目一大致为:
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得他们的和正好是s。
思路:
对于有序数组,用两个指针分别指向头和尾部,然后将他们的和与s比较,若等于s,退出;若<s,则头指针向后移;若>s,则尾指针向前移;直到两个指针相遇。
Java代码
public class MainTest {
public static void main(String[] args) {
int array[] = { 1, 2, 4, 7, 11, 15 };
int s = 15;
int result[] = new int[2];// 存储两个解
result = getTwoSumEqualsNum(array, s);
if (result != null) {// 存在这样的解,输出
System.out.println("一组解为:" + result[0] + "、" + result[1]);
} else {
System.out.println("不存在");
}
}
public static int[] getTwoSumEqualsNum(int[] arr, int num) {
int[] result = new int[2];
int l = 0, r = arr.length - 1;
while (l < r) {
int s = arr[l] + arr[r];
if (s == num) {
result[0] = arr[l];
result[1] = arr[r];
return result;
} else if (s < num) {
l++;
} else {
r--;
}
}
return null;
}
}运行结果:
一组解为:4、11
题目二大致为:
输入一个正数s,打印出所有和为
s
的连续正数序列(至少含有两个数)。
思路:
s
至少要为3,这样才能满足至少含有两个数,若
s>3
,这时可以查找
low
和
high
之间的和,若
=s
,则输出;若
<s
,则增加
high
,若
>s
,则增加
low
,这样就减少了他们之间的数,直到
low<(1+s)/2
;即小于中间值,因为两个中间值的和可能为
s
。
Java代码
public class MainTest {
public static void main(String[] args) {
findSequenceSumEqualsNum(15);
}
public static void findSequenceSumEqualsNum(int num) {
int low = 1, high = 2, sequenceSum = low + high;
int mid = num / 2;
while (low <= mid) {
while (sequenceSum < num && low <= mid) {
high++;
sequenceSum += high;
}
if (sequenceSum == num) {
printSequence(low, high);
}
sequenceSum -= low;
low++;
}
}
private static void printSequence(int low, int high) {
if (low < high) {
for (int i = low; i <= high; i++) {
System.out.print(i + "、");
}
System.out.println();
}
}
}运行结果:
1、2、3、4、5、
4、5、6、
7、8、
面试题42:翻转单词顺序VS左旋转字符串
题目一大致为:
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。
思路:
两次翻转,对于字符串"I am a student.",首先进行第一次翻转,即翻转完为:".tneduts a ma I",然后再对每个单词翻转,最终为:"student. a am I"。
Java实现
public class MainTest {
public static void main(String[] args) {
String s = "I am a student. ";
System.out.println(reverseSentence(s));
}
public static String reverseSentence(String str) {
StringBuilder result = new StringBuilder();
String reverseStr = reverseWord(str);
int start = 0;
for (int i = 0; i < reverseStr.length(); i++) {
if (reverseStr.charAt(i) == ' ') {
result.append(reverseWord(reverseStr.substring(start, i)));
while (i < reverseStr.length() && reverseStr.charAt(i) == ' ') {
result.append(' ');
i++;
}
start = i;
}
}
result.append(reverseStr.substring(start, str.length()));
return result.toString();
}
public static String reverseWord(String word) {
StringBuilder sb = new StringBuilder();
for (int i = word.length() - 1; i >= 0; i--) {
sb.append(word.charAt(i));
}
return sb.toString();
}
}运行结果:
student. a am I
面试题44:扑克牌的顺序
题目大致为:
从扑克牌中随机抽出5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成是任意数字。
思路:
实则本题是判断一个数组是否是连续的,将大、小王看成是0,0可以充当任何的数。这样我的思路是:先对数组排序,排序后统计出0的个数,在计算非0数之间的缺少的数字个数的总和,若是在这个过程中发现前后两个的差为0则为相同的元素,则不符合题意;在满足题意的情况下,若是0的个数大于等于缺少的数字个数的总和,那么满足条件,否则不满足条件。
Java代码:
import java.util.Arrays;
public class MainTest {
public static void main(String[] args) {
// 模拟随机抽牌,大小王为0,A为1,J为11,Q为12,K为13,其实就是个数组,判断数组是否是顺序的
// 测试1:正好填补
int array_1[] = { 0, 0, 1, 4, 5 };
System.out.println(isSequence(array_1));
// 测试2:不能填补
int array_2[] = { 0, 1, 4, 5, 6 };
System.out.println(isSequence(array_2));
// 测试3:有相同元素
int array_3[] = { 0, 1, 3, 3, 4, };
System.out.println(isSequence(array_3));
// 测试4:正好填补
int array_4[] = { 0, 10, 0, 11, 7, };
System.out.println(isSequence(array_4));
}
public static boolean isSequence(int[] arr) {
Arrays.sort(arr);
int zeroNum = 0, gap1Num = 0;
for (int i = 0; i < arr.length - 1; i++) {
if (arr[i] == 0) {
zeroNum++;
} else {
int gap = arr[i + 1] - arr[i];
if (gap == 0) {
return false;
} else {
gap1Num += gap - 1;
}
}
}
if (zeroNum >= gap1Num) {
return true;
}
return false;
}
}运行结果:
true
false
false
true
面试题45:圆圈中最后剩下的数字
题目大致为:
0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
思路:
第一种办法就是通过环形链表模拟这样的删除过程;但是作者推导出了这样的关系式,具体关系式可以见书P231。
Java代码
public class MainTest {
public static void main(String[] args) {
System.out.println(lastNumInCircle(20, 3)); //方法1
System.out.println(lastRemaining(20, 3)); //方法2
System.out.println(lastNumInCircle(20, 23)); //方法1
System.out.println(lastRemaining(20, 23)); //方法2
}
public static int lastNumInCircle(int n, int count) {
ListNode root = new ListNode(-1), p = root;
for (int i = 0; i < n; i++) {
p.next = new ListNode(i);
p = p.next;
}
p.next = root.next;
while (--n > 0) {
for (int i = 0; i < count - 1; i++) {
p = p.next;
}
p.next = p.next.next;
}
return p.value;
}
public static int lastRemaining(int n, int m) {
if (n < 1 || m < 1) {
return -1;
}
int last = 0;
for (int i = 2; i <= n; i++) {
last = (last + m) % i;
}
return last;
}
}运行结果:
19
19
18
18
面试题46:求1+2+...+n
题目大致为:
求1+2+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
Java代码:
public class MainTest {
public static void main(String[] args) {
System.out.println(getSum(10));
}
public static int getSum(int n) {
try {
int[] array = new int[n];
return n + getSum(n - 1);
} catch(Exception e) {
return 0;
}
}
}运行结果:
55
面试题47:不用加减乘除做加法
题目大致为:
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路:
使用位运算。分三步:第一、不考虑进位,用异或模拟加法;第二、用与运算并左移一位模拟进位;第三、重复前面两步。
Java实现
public class MainTest {
public static void main(String[] args) {
System.out.println(add(10, 21));
}
public static int add(int num1, int num2) {
int sum = 0, carry = 0;
do {
sum = num1 ^ num2;
carry = (num1 & num2) << 1;
num1 = sum;
num2 = carry;
} while (num2 != 0);
return num1;
}
}运行结果:
31
本文基于《剑指Offer》编程题的Java实现,涵盖多种算法问题,包括链表操作、数组处理、二叉树问题等,并对部分算法进行了优化。涉及题目有:数值的整数次方、O(1)时间删除链表节点、调整数组顺序、倒数第k个链表节点、反转链表、合并排序链表、二叉树镜像、最小的k个数等。
480

被折叠的 条评论
为什么被折叠?



