二维数组中的查找
-
题设条件
- 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 思路分析
- 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增。因此从左下角开始查找,当要查找数字比左下角数字大时,右移;要查找数字比左下角数字小时,上移。同样也可以从右上角开始查找。 代码实现
//二维数组中的查找
// 测试用例类型:二维数组中包含查找的数字、不包含查找的数字、空指针
public class Solution3 {
public boolean Find(int target, int [][] array) {
if (array == null)
return false;
// 获取数组行列数
int rows = array.length;
int cols = array[0].length;
// 从左下角开始遍历
int row = rows-1, col = 0;
while (row >=0 && col < cols) {
if (array[row][col] < target) {// 目标值大于当前数组元素,向右查找
col++;
} else if (array[row][col] > target) {// 目标值小于当前数组元素,向上查找
row--;
} else {// 查找到
return true;
}
}
return false;// 查找失败
}
}
替换空格
-
题设条件
- 请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 思路分析
- 如果在新开辟的空间进行替换,则只需从前往后逐个字符写入,遇到空格则转换;如果在原始空间进行替换,则需要从后往前替换空格,避免反复移动。 代码实现
- 在新空间替换
public String replaceSpace(StringBuffer str) {
int len = str.length();
StringBuffer sb = new StringBuffer();
// 逐个遍历字符,追加到sb
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
if (ch == ' ')
sb.append("%20");
else
sb.append(ch);
}
return sb.toString();
}
在原始空间进行替换
public static String replaceSpace(StringBuffer str) {
// 首先,遍历数组,确定空格个数
int oldlen = str.length();
int spacenum = 0;
for (int i = 0; i < oldlen; i++) {
if (str.charAt(i) == ' ')
spacenum++;
else
continue;
}
// 然后,根据空格个数,计算新字符串长度,扩容原始字符串空间
int newlen = oldlen + spacenum * 2;
str.setLength(newlen);
// 接着,依次从后往前移动字符,遇到空格则替换
int newIndex = newlen-1;
int oldIndex = oldlen - 1;
// while (oldIndex >= 0 && newIndex >= 0) {
while (oldIndex != newIndex) {// 当两个索引相遇时,表示空格已经替换完毕,不需要再移动前面的字符
if (str.charAt(oldIndex) == ' ') {
str.setCharAt(newIndex--, '0');
str.setCharAt(newIndex--, '2');
str.setCharAt(newIndex--, '%');
oldIndex--; // 容易出错
} else {
str.setCharAt(newIndex--, str.charAt(oldIndex--));
}
}
return str.toString();
}
-
复杂度分析
- 时间复杂度O(n),空间复杂度O(1)
从尾到头打印链表
-
题设条件
- 输入一个链表,从尾到头打印链表每个节点的值。 思路分析
- 使用递归的方法 代码实现
// 方法1,使用递归的方法
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
if (listNode != null) {// 递归进行的条件
list = printListFromTailToHead(listNode.next);
list.add(listNode.val);
} else {// 递归结束
;
}
return list;
}
-
复杂度分析
- 时间复杂度O(n),空间复杂度O(n)
利用栈的FILO特性
-
思路分析
- 先从头遍历链表,元素依次入栈 代码实现
// 方法2,利用栈实现
public ArrayList<Integer> printListFromTailToHead2(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<Integer>();
Stack<Integer> stack = new Stack<Integer>();
while (listNode != null) {
stack.push(listNode.val);
listNode = listNode.next;
}
while (!stack.isEmpty())
list.add(stack.pop());
return list;
}
-
复杂度分析
- 时间复杂度O(n),空间复杂度O(n)
重建二叉树
-
题设条件
- 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。 思路分析
- 前序遍历的第一个元素为根元素,在中序遍历中查找根元素的索引rootIndex。通过rootIndex拆分中序遍历结果in,得到左子树的中序遍历leftin和右子树的中序遍历rightin;通过rootIndex拆分前序遍历结果pre,得到左子树的前序遍历leftpre和右子树的前序遍历rightpre。递归方法进行以上操作,直到数组长度为1时返回叶子节点,或为0时返回空节点。 代码实现
public static TreeNode reConstructBinaryTree(int [] pre,int [] in) {
// 数组长度大于1时进行递归,等于1时返回叶子节点,小于1时返回null;
int len = pre.length;// 数组长度,两个数组等长
if (len < 1)
return null;
if (len == 1)
return new TreeNode(pre[0]);
// 递归进行条件
int rootIndex = 0;// 根节点在中序遍历中的索引
TreeNode root = null;// 根当前子树的根节点,作为结果返回
// 找出rootIndex
for (int i = 0; i < in.length; i++) {
if (in[i] == pre[0]) {
rootIndex = i;
break;
}
}
// 左子树有rootIndex个元素,右子树部分有len - 1 - rootIndex个元素
int[] leftpre = Arrays.copyOfRange(pre, 1, rootIndex+1); // 前序遍历中左子树部分,索引[1, rootIndex]
int[] rightpre = Arrays.copyOfRange(pre, rootIndex + 1, len); // 前序遍历的右子树分,索引[rootIndex+1, len-1]
int[] leftin = Arrays.copyOfRange(in, 0, rootIndex); // 中序遍历的左子树部分,索引[0, rootIndex-1]
int[] rightin = Arrays.copyOfRange(in, rootIndex + 1, len); // 中序遍历的右子树部分,索引[rootIndex+1, len - 1]
TreeNode rootNode = new TreeNode(pre[0]);
rootNode.left = reConstructBinaryTree(leftpre, leftin);
rootNode.right = reConstructBinaryTree(rightpre, rightin);
return rootNode;
}
两个栈实现队列
-
题设条件
- 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。 代码实现
import java.util.Stack;
public class Solution {
//用stack1作为主要栈,入队push操作都针对这个栈
//用stack2作为辅助栈,出队pop操作时先判断stack2是否为空。若stack2为空,则现将stack1栈元素压入stack2,然后stack2出栈;
//若stack2不为空,则stack2直接出栈
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
int res = 0;
if (stack2.isEmpty()) {
while (!stack1.isEmpty())
stack2.push(stack1.pop());
res = stack2.pop();
} else {
res = stack2.pop();
}
return res;
}
}
-
复杂度分析
- 最坏情况为n个元素先依次进栈,然后依次出栈。时间复杂度为O(n),空间复杂度为O(n)
斐波那契数列
-
题设条件
-
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39
代码实现
public class Solution {
public int Fibonacci(int n) {
int first = 0, second = 1;// 0-1项
int tmp = 0;
for (int i = 1; i <= n; i++) {// 2-(n-1)项
tmp = first + second;
first = second;// first所指向的为新生成的项
second = tmp;
}
return first;
}
}
跳台阶
-
题设条件
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 思路分析
- 当位于第n级台阶时,上一步可以是第n-1级台阶,也可以是第n-2级台阶,所以等于前两种情况的加和。当n==1时,有1种情况;当n==2时,有2中情况。 代码实现
public class Solution {
public int JumpFloor(int target) {
if (target == 1)
return 1;
else if (target == 2)
return 2;
else
return JumpFloor(target - 1) + JumpFloor(target - 2);
}
}
变态跳台阶
-
题设条件
- 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 思路分析
-
n==1时,只有一种情况,0-1;n == 2时,有两种情况0-2、0-1-2,即从第0级跳过来和从第1级跳过来;n==3时,有四种情况0-3、0-1-3、0-2-3、0-1-2-3,即分别从第0级、1级、2级跳过来……依次类推,在第n级台阶时,可能从之前每一集台阶跳过来。
设n==0时,有一种情况,则f(n) = f(0) + f(1) + …… + f(n-1)
其中f(0)=1,f(1)=1
有如下递推公式:
f(n) = f(0) + f(1) + …… + f(n-2) + f(n-1)
f(n-1)=f(0) + f(1) + …… + f(n-2)
所以,f(n) = 2*f(n-1)。
-
代码实现
public int JumpFloorII(int target) {
if (target == 0)
return 1;
else
return (int)Math.pow(2, target-1);
}
利用移位运算的性质,改进版
public int JumpFloorII(int target) {
if (target == 0)
return 1;
else
return 1 << (target-1);
}
矩形覆盖问题
-
题设条件
- 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 思路分析
- n==1时,有1种方法;n==2时,有2种方法;n==3时,有3种方法;……;n==k时,取决于第一个矩形是横着放还是竖着放。所以,
f(n) = f(n-1) + f(n-2), f(1)=1, f(2)=2
-
代码实现
public int RectCover(int target) {
if (target == 0)
return 0;
else {
int first = 1, second = 1;
int sum = 0;
for (int i=0; i< target; i++) {
sum = first + second;
first = second;
second = sum;
}
return first;
}
}
二进制中1的个数
-
题设条件
- 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 思路分析
数值的整数次方
-
题设条件
- 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 思路分析
- exponent>0时,每次循环乘以base;exponent<0时,每次循环除以base。 代码实现
public double Power(double base, int exponent) {
double res = 1.0;
if (exponent > 0) {
while (exponent > 0) {
res *= base;
exponent--;
}
} else if (exponent < 0) {
while (exponent < 0) {
res /= base;
exponent++;
}
} else {
;
}
return res;
}
反转链表
-
代码实现
public ListNode ReverseList(ListNode head) {
// if (head == null || head.next == null)// 链表为null或长度为1时,直接返回
// return head;
ListNode pre = null, curr = head, succ = null;
while (curr != null) {
succ = curr.next;
curr.next = pre;
pre = curr;
curr = succ;
}
head = pre;
return head;
}
合并两个排序单链表
-
题设条件
- 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 代码实现
- 非递归、原地合并,时间复杂度O(m+n),空间复杂度O(1)
public ListNode Merge2(ListNode list1,ListNode list2) {
if (list1 == null && list2 == null) {// 处理链表为空情况
return null;
} else if (list1 == null) {
return list2;
} else if (list2 == null) {
return list1;
}
// 链表均非空
ListNode head = new ListNode(0);// 头结点
ListNode curr1 = list1, curr2 = list2, curr = head;// 移动指针
while (curr1 != null && curr2 != null) {
if (curr1.val < curr2.val) {
curr.next = curr1;
curr = curr.next;
curr1 = curr1.next;
} else {
curr.next = curr2;
curr = curr.next;
curr2 = curr2.next;
}
}
if (curr1 != null)
curr.next = curr1;
if (curr2 != null)
curr.next = curr2;
return head.next;
}
非递归、创建新链表,时间复杂度O(m+n),空间复杂度O(m+n)
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = new ListNode(0);
ListNode curr1 = list1, curr2 = list2, curr = head;
while (curr1 != null && curr2 != null) {
if (curr1.val > curr2.val) {
curr.next = new ListNode(curr2.val);
curr = curr.next;
curr2 = curr2.next;
} else {
curr.next = new ListNode(curr1.val);
curr = curr.next;
curr1 = curr1.next;
}
}
while (curr1 != null) {
curr.next = new ListNode(curr1.val);
curr = curr.next;
curr1 = curr1.next;
}
while (curr2 != null) {
curr.next = new ListNode(curr2.val);
curr = curr.next;
curr2 = curr2.next;
}
return head.next;
}
递归版本,时间复杂度O(m+n),空间复杂度O(n*n)
二叉树的镜像
-
题设条件
-
操作给定的二叉树,将其变换为源二叉树的镜像。
代码实现
public void Mirror(TreeNode root) {
if (root == null || root.left == null && root.right == null) {// 当前节点null时,结束;左右子树均为空时,结束递归
return;
}
// 递归进行条件
// 交换左右子树
TreeNode tmp = null;
tmp = root.left;
root.left = root.right;
root.right = tmp;
Mirror(root.left);
Mirror(root.right);
}
二进制中1的个数
-
题设条件
- 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 思路分析
- 对于正数,存储过程中按源码存储,直接对2求余数即可得到源码表示中的“1”;对于负数,按补码存储,题设条件是找出补码中“1”的个数,所以也可以直接对2求余数得到补码形式中的“1”。每求一次余数后,需要对数字除以2,正数右移一位即可, 负数需要无符号右移。注意,负数对2求余数得到负数,即首次求余数得到负数,所以不能用是否等于1来判断。 代码实现
public int NumberOf1(int n) {
int count = 0;
while (n != 0) {
if (n % 2 != 0) // 负数求余得到负数
count++;
n = n >>> 1; // 负数需要无符号右移
}
return count;
}
除了不停地对n求余、移位外,还可以通过 位与 运算检测不同位上的1
-
代码实现
public int NumberOf1(int n) {
int count = 0;
int mask = 1;
for (int i = 1; i <= 32; i++) {
if ((mask & n) != 0)
count++;
mask = mask << 1;
}
return count;
}
包含min函数的栈
-
题设条件
- 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。 思路分析
- 用一个辅助栈minStack存储元素最小值,栈顶元素代表最小值;用numStack存储每次入栈的元素。当一个元素入栈numStack时,若当前入栈元素小于等于minStack的栈顶,则将该元素入栈minStack;否则,不如minStack栈。numStack出栈时,若出栈元素等于minStack栈顶,则minStack跟着出栈。 代码实现
import java.util.Stack;
public class Solution {
Stack<Integer> minStack = new Stack<>();// 存储最小值
Stack<Integer> numStack = new Stack<>();// 存储入栈数据
public void push(int node) {
numStack.push(node);
if (minStack.isEmpty() || minStack.peek() >= node)// 入栈元素小于等于当前最小值时,元素入minStack栈
minStack.push(node);
}
public void pop() {
if (numStack.peek() == minStack.peek())
minStack.pop();
numStack.pop();
}
public int top() {
return numStack.peek();
}
public int min() {
return minStack.peek();
}
}
从上往下打印二叉树
-
题设条件
- 从上往下打印出二叉树的每个节点,同层节点从左至右打印。 思路分析
- 二叉树的层次遍历,借助一个队列存储每层待访问节点 代码实现
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<TreeNode>();
ArrayList<Integer> res = new ArrayList<>();
if (root == null)
return res;
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.remove();// 出队元素
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
res.add(node.val);
}
return res;
}
链表中倒数第k个节点
-
题设条件
- 输入一个链表,输出该链表中倒数第k个结点。 思路分析
- 用双指针法,left、right指针初始位置均指向第一个节点。第一步,left保持不动,right后移k步;第二步,两个指针同时后移,直到right指向为空,此时left所指向的即为倒数第k个节点。 代码实现
public ListNode FindKthToTail(ListNode head,int k) {
ListNode left = head, right = head;
for (int i = 0; i < k; i++) {
if (right == null)
return null;
else
right = right.next;
}
while (right != null) {
left = left.next;
right = right.next;
}
return left;
}
调整数组顺序使奇数位于偶数前面
-
题设条件
- 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。 思路分析
- 可以借鉴稳定的排序方法,如冒泡排序、归并排序、直接插入排序等;也可以新开辟一个等长数组,进行元素复制。 代码实现
- 新开辟一个等长数组,进行元素复制。时间复杂度O(n),空间复杂度O(n)
public void reOrderArray(int [] array) {
int len = array.length;
int[] tmp = new int[len];// 辅助数组
// 首先,对原始数组元素进行复制
for (int i =0; i < len; i++) {
tmp[i] = array[i];
}
// 然后,第一次遍历辅助数组tmp,将奇数元素复制到原始数组前半部分
int index = 0;// array数组的索引
for (int i = 0; i < len; i++) {
if (tmp[i] % 2 != 0) {// 负奇数求余得到负数,所以不能用等于1来判断
array[index++] = tmp[i];
} else {
continue;
}
}
// 第二次遍历辅助数组,将偶数元素复制到原始数组的后半部分
for (int i = 0; i < len; i++) {
if ((tmp[i] & 1) == 0) {// 利用位与运算,取二进制的最低位
array[index++] = tmp[i];
} else {
continue;
}
}
return;
}
借鉴直接插入排序的思想,将奇数插入到前面
public void reOrderArray(int [] array) {
int len = array.length;
for (int i = 1; i < len; i++) { // 外层索引,指向待插入的元素
if ((array[i] & 1) == 0) { // 若当前待插入元素为偶数,则跳过
continue;
} else { // 对奇数元素进行插入处理
int tmp = array[i];
int j = 0;
for(j = i-1; j >= 0 && (array[j] % 2 == 0); j--) { // 内层索引,指向待检查的元素
array[j+1] = array[j];
}
array[j+1] = tmp;
}
}
}
借鉴冒泡排序思想,若当前元素为偶数,则交换相邻元素。交换的条件是当前元素为偶数,下一个元素为奇数
public void reOrderArray(int [] array) {
int len = array.length;
for (int i = 1; i < len; i++) { // 外层循环,控制趟数
for (int j = 0; j < len - i; j++) {
if (array[j]%2 == 0 && array[j+1]%2 != 0) {
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
借鉴归并排序的思想
public void reOrderArray(int [] array) {
split(array, 0, array.length-1);
}
public void split(int[] array, int start, int end) {
if (start < end) {
int mid = (start +end) >> 1;
split(array, start, mid);
split(array, mid+1, end);
merge(array, start, mid, mid+1, end);
} else {
return;
}
}
public void merge(int[] array, int start1, int end1, int start2, int end2) {
int len = end2 - start1 + 1;
int[] tmp = new int[len];
// 首先,将原始数组的元素归并到辅助数组:先复制array1的奇数元素,再复制array2的奇数元素,接着复制array1的偶数元素以及array2的偶数元素
int i = 0;
for (int j = 0; j < len; j++) {
if (array[start1 + j] % 2 != 0) {
tmp[i++] = array[start1 + j];
}
}
for (int j = 0 ; j < len; j++) {
if (array[start1 + j] % 2 == 0) {
tmp[i++] = array[start1 + j];
}
}
// 然后,将归并后的元素复制到原始数组
for (int j = 0; j < len; j++) {
array[start1 + j] = tmp[j];
}
}
顺时针打印矩阵
-
题设条件
- 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。 思路分析
- 用两个定位点来定位待打印的范围。本题分别用左上角和右下角来定位,每打印完一圈,左上角、右下角的索引均向内移动一次,外层循环的条件是rowMin 如果行列均为偶数,则外层循环结束后rowMin==rowMax && colMin==colMax,剩余一个中心点需要额外处理;如果行为奇数,则外层循环结束后colMin 代码实现
public ArrayList<Integer> printMatrix(int[][] matrix) {
ArrayList<Integer> res = new ArrayList<>();
if (matrix == null)
return res;
int rows = matrix.length, cols = matrix[0].length; // 获取行列数
int rowMin = 0, colMin = 0, rowMax = rows - 1, colMax = cols - 1; // 定义行的最大最小值、列的最大最小值,用来标识待打印的左上角和右下角
while (rowMin < rowMax && colMin < colMax) { // 左上角和右下角不重合
for (int i = colMin; i < colMax; i++) {// 从左到右打印第一行
res.add(matrix[rowMin][i]);
}
for (int i = rowMin; i < rowMax; i++) {// 从上到下打印最后一列
res.add(matrix[i][colMax]);
}
for (int i = colMax; i > colMin; i--) {// 从右到左打印最后一行
res.add(matrix[rowMax][i]);
}
for (int i = rowMax; i > rowMin; i--) {// 从下到上打印第一列
res.add(matrix[i][colMin]);
}
rowMin++;
colMin++;
rowMax--;
colMax--;
}
// 处理只剩下一行或一列的情况
if (colMin < colMax && rowMin == rowMax) { // 只剩下一行,从左至右打印
for (int i = colMin; i <= colMax; i++)
res.add(matrix[rowMin][i]);
} else if (rowMin < rowMax && colMin == colMax) { // 只剩下一列,从上至下打印
for (int i = rowMin; i <= rowMax; i++)
res.add(matrix[i][colMin]);
} else if (rowMin == rowMax && colMin == colMax) { // 只剩下一个中心点
res.add(matrix[rowMin][colMin]);
}
return res;
}
树的子结构
-
题设条件
- 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) 思路分析
-
首先从根节点开始检索。若根节点值相等,则开始从根节点匹配;若根节点不相等或根节点匹配失败,则递归检索左子树;若左子树检索失败,则递归检索右子树。
匹配过程也是递归,递归结束条件有三个
1)root2==null, 2)root1==null && root2!=null, 3)root1.val!=root2.val
;
若root1.val==root2.val,则递归匹配左右子树。
代码实现
public class Solution3 {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean res = false;
if (root1 != null && root2 != null) {
if (root1.val == root2.val) { // 开始匹配
res = tree1HasTree2(root1, root2);
}
if (!res) { // 根节点匹配失败,检索左子树
res = HasSubtree(root1.left, root2);
}
if (!res) { // 左子树也匹配失败,检索右子树
res = HasSubtree(root1.right, root2);
}
}
return res;
}
public boolean tree1HasTree2(TreeNode root1, TreeNode root2) {
// 递归结束的三种情况:1)root2==null, 2)root1==null && root2!=null, 3)root1.val!=root2.val
if (root2 == null)
return true;
else if (root2 != null && root1 == null)
return false;
else if (root1.val != root2.val)
return false;
else // 递归进行
return tree1HasTree2(root1.left, root2.left) && tree1HasTree2(root1.right, root2.right);
}
}
栈的压入、弹出序列
-
题设条件
- 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 思路分析
- 借用一个辅助的栈stack。如果出栈序列的当前元素是栈顶元素,则直接弹出;如果出栈序列的当前元素不是栈顶元素,则入栈;如果栈为空,则入栈。 代码实现
public boolean IsPopOrder(int [] pushA,int [] popA) {
if (pushA.length != popA.length)
return false;
int len = pushA.length;
Stack<Integer> stack = new Stack<>(); // 辅助栈
int outIndex = 0, inIndex = 0; // 出栈、入栈序列的索引
for (inIndex = 0; inIndex < len; inIndex++) {// 遍历入栈序列
stack.push(pushA[inIndex]);
while(!stack.isEmpty() && stack.peek()==popA[outIndex]) {// 如果当前栈顶元素与当前出栈元素相等,则出栈
stack.pop();
outIndex++;
}
}
return stack.isEmpty();
}
二叉搜索树的后序遍历序列
-
题设条件
- 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 思路分析
- 对于后序遍历序列,最后一个节点是根节点,根节点可以将其之前的序列分为两部分,用递归的方法不断拆分序列数组。递归结束条件:1)序列长度为1时返回true;2)拆分后的子序列中存在和根节点不满足大小关系的元素,返回false。 代码实现
public class Solution3 {
public boolean VerifySquenceOfBST(int [] sequence) {
if (sequence == null || sequence.length == 0)
return false;
else if (sequence.length == 1)
return true;
else
return VerifySquenceOfBST(sequence, 0, sequence.length-1);
}
public boolean VerifySquenceOfBST(int[] sequence, int start, int end) {
// 递归结束条件
if (start == end) {
return true;
}
int root = sequence[end]; // 找到当前子树的根节点
int index = start; // 代表右子树的子序列第一个索引
// 找出左右子树的分割点
for (index = start; index < end; index++) {
if (sequence[index] < root)
continue;
else
break;
}
for (int i = index; i < end; i++) {
if (sequence[i] > root)
continue;
else
return false;
}
if (index == start) { // 只存在右子树
return VerifySquenceOfBST(sequence, index, end-1);
} else if (index == end) { // 只存在左子树
return VerifySquenceOfBST(sequence, start, index-1);
} else { // 左右子树均存在
return VerifySquenceOfBST(sequence, start, index-1) && VerifySquenceOfBST(sequence, index, end);
}
}
}
复杂链表的复制
-
题设条件
- 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) 思路分析
-
分三步走:第一步复制每个节点,并插入到对应节点后面;第二步,遍历链表,复制每个节点的特殊指针;第三部,拆分链表。
代码实现
class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
public class Solution3 {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
// 第一步,复制每个节点,插入到原节点后面
RandomListNode curr = pHead;
while (curr != null) {
RandomListNode node = new RandomListNode(curr.label);
node.next = curr.next;
curr.next = node;
curr = node.next;// 注意,node节点已经插入到了curr后面
}
// 第二步,复制每个节点的兄弟连接
curr = pHead;
while (curr != null) {
if (curr.random != null)
curr.next.random = curr.random.next; // 容易出错,random域可能为null
curr = curr.next.next;;
}
// 第三步,拆分链表
RandomListNode newHead = pHead.next;
RandomListNode curr1 = pHead, curr2 = newHead;
while (curr2.next != null) { // 存在后继节点,至少两个
curr1.next = curr1.next.next;
curr2.next = curr2.next.next;
curr1 = curr1.next;
curr2 = curr2.next;
}
// 对最后两个节点进行处理,即curr2.next==null时
curr1.next = null;
curr2.next = null;
return newHead;
}
}
数组中出现次数超过一半的数字
-
题设条件
- 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 代码实现
public int MoreThanHalfNum_Solution(int [] array) {
if (array == null || array.length == 0)
return 0;
int len = array.length;
int ele = 0;
int count = 0;
// 找出可能的众数
for (int i = 0; i < len; i++) {
if (count == 0) {
ele = array[i];
count++;
} else if (array[i] == ele) {
count++;
} else {
count--;
}
}
// 确认是否是众数
count = 0;
for (int arr : array)
if (arr == ele)
count++;
if (count > (len>>1))
return ele;
else
return 0;
}
最小的k个数
-
题设条件
- 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。 思路分析
- 利用排序二叉树集合TreeSet,利用TreeSet的有序特性,只保存前k个数字 代码实现
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if (input == null || input.length < k)
return res;
TreeSet<Integer> set = new TreeSet<>();
for (int item : input) {
set.add(item);
// 若当前容量超过k,则移除最大元素
if (set.size() > k)
set.remove(set.last());
}
for (int item : set)
res.add(item);
return res;
}
-
复杂度分析
- 时间复杂度O(n*log(k)),空间复杂度O(k)
也可以利用冒泡排序的思想,排序k趟即可将最小的k个数排到前面
-
代码实现
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> res = new ArrayList<>();
if (input == null || input.length < k)
return res;
int len = input.length;
for (int i = 0; i < k; i++) { // 外层循环每进行一次,排序完成一个元素
for (int j = len-1; j > i; j--) {
if (input[j] < input[j-1]) {
int tmp = input[j];
input[j] = input[j-1];
input[j-1] = tmp;
}
}
}
for (int i = 0; i < k; i++)
res.add(input[i]);
return res;
}
-
复杂度分析
- 时间复杂度O(n*k),空间复杂度O(1)
连续子数组的最大和
-
题设条件
- 在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。 代码实现
public int FindGreatestSumOfSubArray(int[] array) {
if (array == null || array.length < 1)
return Integer.MIN_VALUE;
int sum = 0; // 存储当前子序列的累加和
int maxSum = Integer.MIN_VALUE; // 存储最大和
int len = array.length;
for (int i = 0; i < len; i++) {
sum += array[i];
maxSum = Math.max(maxSum, sum);
if (sum < 0)
sum = 0;
}
return maxSum;
}
把数组排成最小的数
-
题设条件
- 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 思路分析
- 要使拼接输出的字符串表示的数字最小,需要对原始数组进行排序。对原始数组的元素a、b,应该定义一种比较规则,使得若a
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if (numbers == null || numbers.length == 0)
return "";
ArrayList<String> arrlist = new ArrayList<>();
// 将数字转换为字符串,存放在ArrayList中
for (int num : numbers)
arrlist.add(new Integer(num).toString());
// 对ArrayList中的字符串排序
Collections.sort(arrlist, new Comparator<String>() {
public int compare(String o1, String o2) {
String str1 = o1+o2;
String str2 = o2+o1;
return str1.compareTo(str2);
};
});
// 对排序后的字符串进行拼接输出
StringBuffer sb = new StringBuffer();
for (String str : arrlist)
sb.append(str);
return sb.toString();
}
}