文章目录
- 数组中重复的数字
- 二维数组中的查找
- 原地替换字符串空格
- 从尾到头打印链表
- 重建二叉树
- 二叉树的下一个结点
- 用两个栈实现队列
- 斐波那契数列
- 变态青蛙跳台阶
- 旋转数组的最小数字
- 矩阵中的路径
- 机器人的运动范围
- 剪绳子
- 二进制中1的个数
- 数值的整数次方
- 打印从1到最大的n位数
- O(1)时间内,删除链表中给定结点
- 删除链表中重复的结点
- 正则表达式匹配
- 表示数值的字符串
- 调整数组顺序使奇数位于偶数前边并保持相对顺序
- 链表中倒数第k个结点
- 链表中环的入口结点
- 反转链表
- 合并两个排序的链表
- 二叉树的镜像
- 对称的二叉树
- 顺时针打印矩阵
- 包含min函数的栈
- 栈的压入、弹出序列
- 从上往下打印二叉树
- 按之字形顺序打印二叉树
- 二叉搜素树的后序遍历序列
- 二叉树中和为某一值的路径
- 复杂链表的复制
- 二叉搜索树与双向链表
- 序列化二叉树
- 字符串的排列
- 数组中出现次数超过一半的数字
- 最小的K个数
- 数据流中的中位数
- 连续子数组的最大和
- 整数中1出现的次数
- 把数组排成最小的数
- 把数字翻译成字符串
- 礼物的最大值
- 最长不含重复字符的子字符串
- 丑数(只含2、3、5因子的数字)
- 第一个只出现一次的字符
- 字符流中第一个不重复的字符
- 数组的逆序对数
- 两个链表的第一个公共结点
- 数字在排序数组中出现的次数
- 二叉搜索树的第K个结点
- 二叉树的深度
- 数组中出现一次的数字
- 数组中唯一只出现一次的数字,其他数字出现3次
- 和为sum的两个数字
- 和为S的连续正数序列
- 反转单词顺序列
- 左旋转字符串
- 滑动窗口的最大值
- n个骰子的点数
- 构建乘积数组
- 扑克牌顺子
- 约瑟夫环
- 不用加减乘除做加法
- 把二叉树打印成多行
- 股票的最大利润
- 加法
数组中重复的数字
解法一:排序、遍历
解法二:哈希计数
解法三:
class Solution {
public int findRepeatNumber(int[] nums) {
// 利用数字特点,逐个插桩归位,如果位置上已经是对应值,说明重复
for (int i = 0; i < nums.length; i++) {
while (nums[i] != i) {
int temp = nums[i];
if (temp == nums[temp])
return temp;
nums[i] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}
二维数组中的查找
public class Solution {
public boolean Find(int target, int [][] array) {
int row = array.length;
int col = array[0].length;
// 解法一
// 逐行二分查找
/*
for (int i = 0; i < row; i++) {
int left = 0, right = col - 1;
while (left <= right) {
int mid = (left + right) >>> 1;
if (array[i][mid] == target)
return true;
else if (array[i][mid] > target)
right = mid - 1;
else
left = mid + 1;
}
}
return false;
*/
// 解法二
// 站在右上角看数组,其实是个二叉排序树,所以可以按照二叉排序树的查找方式进行二分查找
// 从右上角开始比较
int m = 0;
int n = col - 1;
while (m < row && n >= 0) {
if (matrix[m][n] == target)
return true;
// 若小于,那么该行前边的所有元素肯定都小于,直接下一行
else if (matrix[m][n] < target)
m++;
// 若大于,该列下边所有元素肯定都大于,直接往前走一列
else
n--;
}
return false;
}
}
原地替换字符串空格
class Solution {
public:
void replaceSpace(char *str,int length) {
if (str == NULL)
return;
int numOfBlank = 0;
int i = 0;
// 统计空格个数
while (str[i] != '\0') {
if (str[i] == ' ')
numOfBlank++;
i++;
}
// 双指针
// 找到新字符串的末尾位置,从后往前替换
int newLen = length + numOfBlank * 2;
while (length >= 0 && newLen > length) {
if (str[length] == ' ') {
str[newLen--] = '0';
str[newLen--] = '2';
str[newLen--] = '%';
} else {
str[newLen--] = str[length];
}
length--;
}
}
};
// 从前往后,没啥意思
class Solution {
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
if (c == ' ')
sb.append("%20");
else
sb.append(c);
}
return sb.toString();
}
}
// 从后往前,学习
public class Solution {
public String replaceBlank(String input) {
if (input == null) {
return null;
}
int blankNum = 0;
int length = input.length();
int newLength = 0;
for (int i = 0; i < length; i++) {
if (input.charAt(i) == ' ') {
blankNum++;
}
}
// 替换后的字符串长度
newLength = length + 2 * blankNum;
char[] newChars = new char[newLength];
int index = newLength - 1;
for (int i = length - 1; i >= 0; i--) {
char c = input.charAt(i);
if (c == ' ') {
newChars[index--] = '0';
newChars[index--] = '2';
newChars[index--] = '%';
} else {
newChars[index--] = c;
}
}
return new String(newChars);
}
}
从尾到头打印链表
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
// 解法一:栈
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
Stack<ListNode> stack = new Stack<>();
ArrayList<Integer> list = new ArrayList<>();
while (listNode != null) {
stack.push(listNode);
listNode = listNode.next;
}
while (!stack.empty()) {
list.add(stack.pop().val);
}
return list;
}
// 解法二:递归,其实还是栈...
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
if (listNode != null) {
if (listNode.next != null) {
list = printListFromTailToHead(listNode.next);
}
list.add(listNode.val);
}
return list;
}
}
重建二叉树
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
private int[] pre;
private int[] in;
public TreeNode reConstructBinaryTree(int [] pre, int [] in) {
if (pre == null || pre.length == 0)
return null;
this.pre = pre;
this.in = in;
return create(0, pre.length - 1, 0, in.length - 1);
}
private TreeNode create(int preL, int preR, int inL, int inR) {
if (preL > preR || inL > inR)
return null;
TreeNode root = new TreeNode(pre[preL]);
// 寻找中序遍历中根节点的位置
int k;
for (k = inL; k < inR; k++) {
if (in[k] == pre[preL])
break;
}
int len = k - inL;
root.left = create(preL + 1, preL + len, inL, k - 1);
root.right = create(preL + len + 1, preR, k + 1, inR);
return root;
}
}
二叉树的下一个结点
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if (pNode == null)
return null;
// 如果结点有右孩子,那么下一个结点是右孩子的最左结点
if (pNode.right != null) {
TreeLinkNode node = pNode.right;
while (node.left != null)
node = node.left;
return node;
// 否则,如果结点的父结点不为空
}
/*
else if (pNode.next != null) {
// 那么往上追溯,直到找到一个结点,是其父结点的左孩子
// 那么下一个结点就是这个结点的父节点。
// 如果没有这样的结点则返回null
// 这里涵盖了该结点自身就是左孩子的情况,那么循环会直接返回,
// 即它的父结点就是中序遍历的下一个结点
TreeLinkNode nextNode = pNode.next;
TreeLinkNode curNode = pNode;
while (nextNode != null && curNode != nextNode.left) {
curNode = nextNode;
nextNode = nextNode.next;
}
return nextNode;
}
return null;
*/
// 另一种写法
while (pNode.next != null) {
if (pNode.next.left == pNode)
return pNode.next;
pNode = pNode.next;
}
return null;
}
}
用两个栈实现队列
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if (stack2.empty()) {
if (stack1.isEmpty())
return -1;
while (!stack1.empty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
斐波那契数列
public class Solution {
public int Fibonacci(int n) {
if (n <= 0)
return 0;
if (n == 1)
return 1;
int a = 0;
int b = 1;
int MOD = 1_000_000_007
for (int i = 2; i <= n; i++) {
// 这里针对溢出的判断也可以采用减法,即如果 c 大于MOD,就减去MOD
int c = (a + b) % MOD;
a = b;
b = c;
}
return b;
}
}
青蛙跳台阶也是斐波那契:
青蛙一次能跳1个或2个台阶,问跳上n个台阶有几种跳法?
public class Solution {
public int JumpFloor(int target) {
if (target <= 2)
return target;
int a = 1;
int b = 2;
for (int i = 3; i <= target; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
}
矩形覆盖也是斐波那契:
用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
public class Solution {
public int RectCover(int target) {
if (target <= 2)
return target;
int a = 1;
int b = 2;
for (int i = 3; i <= target; i++) {
int c = a + b;
a = b;
b = c;
}
return b;
}
}
变态青蛙跳台阶
public class Solution {
public int JumpFloorII(int target) {
if (target <= 1)
return 1;
int count = 0;
// 由于这次可以一下跳任意多个台阶,所以最后一步可能从任意一个位置到达
// 所以总次数等于之前所有台阶的次数之和,而不是简单的前两个位置次数之和
for (int i = 1; i <= target; i++) {
count += JumpFloorII(i - 1);
}
return count;
}
}
另外,可以发现,这次上n个台阶次数为2的n-1次方
public class Solution {
public int JumpFloorII(int target) {
return (int)Math.pow(2.0, target - 1);
}
}
旋转数组的最小数字
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
int left = 0;
int right = array.length - 1;
while (left < right) {
int mid = (left + right) >>> 1;
if (array[mid] > array[right])
left = mid + 1;
else if (array[mid] < array[right])
right = mid;
else
right--;
}
return array[right];
}
}
矩阵中的路径
public class Solution {
private boolean[][] visited;
private int rows;
private int cols;
private char[] str;
private char[] matrix;
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
if (matrix == null || rows < 1 || cols < 1 || str == null)
return false;
this.visited = new boolean[rows][cols];
this.rows = rows;
this.cols = cols;
this.str = str;
this.matrix = matrix;
int index = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (hasPath0(i, j, index))
return true;
}
}
return false;
}
private boolean hasPath0(int x, int y, int index) {
// 如果当前下标已经达到给定字符串的长度,则说明已经匹配完成,返回 true
if (index == str.length)
return true;
boolean hasPath = false;
// 判断边界条件、是否访问过、当前对应元素是否相等
boolean flag = x >= 0 && x < rows
&& y >= 0 && y < cols
&& !visited[x][y]
&& matrix[x * cols + y] == str[index];
// 如果满足条件,则尝试这一步
if (flag) {
// 匹配长度加1
index++;
// 标记当前位置已经访问
visited[x][y] = true;
// 递归周围四个位置
hasPath = hasPath0(x + 1, y, index)
|| hasPath0(x - 1, y, index)
|| hasPath0(x, y + 1, index)
|| hasPath0(x, y - 1, index);
// 若当前周围四个都不满足,则说明这一点是死胡同,需要回溯
// 并且要将当前位置的访问标志恢复为 false
if (!hasPath) {
visited[x][y] = false;
}
}
return hasPath;
}
}
机器人的运动范围
类似上题,回溯
public class Solution {
private int rows;
private int cols;
private int threshold;
private boolean[][] visited;
public int movingCount(int threshold, int rows, int cols) {
if (threshold < 0 || rows <= 0 || cols <= 0)
return 0;
this.rows = rows;
this.cols = cols;
this.threshold = threshold;
this.visited = new boolean[rows][cols];
return helper(0, 0);
}
private int helper(int x, int y) {
int count = 0;
boolean flag = x >= 0 && x < rows
&& y >= 0 && y < cols
&& !visited[x][y]
&& getDigitSum(x) + getDigitSum(y) <= threshold;
if (flag) {
visited[x][y] = true;
count = 1 + helper(x - 1, y)
+ helper(x + 1, y)
+ helper(x, y - 1)
+ helper(x, y + 1);
}
return count;
}
private int getDigitSum(int x) {
int sum = 0;
while (x > 0) {
sum += x % 10;
x /= 10;
}
return sum;
}
}
剪绳子
public class Solution {
private int[] dp;
public int cutRope(int target) {
if (target < 2)
return 0;
if (target == 2)
return 1;
if (target == 3)
return 2;
/*
// 动态规划
dp = new int[target + 1];
dp[1] = 1;
dp[2] = 2;
dp[3] = 3;
int max;
for (int i = 4; i <= target; i++) {
max = 0;
for (int j = 1; j <= i / 2; j++) {
int count = dp[j] * dp[i - j];
max = Math.max(max, count);
}
dp[i] = max;
}
return dp[target];
*/
// 贪心
int timesOf3 = target / 3;
if (target % 3 == 1)
timesOf3--;
int timesOf2 = (target - timesOf3 * 3) / 2;
return (int)Math.pow(3, timesOf3) * (int)Math.pow(2, timesOf2);
}
}
二进制中1的个数
public class Solution {
public int NumberOf1(int n) {
/*
// Java解法
int count = 0;
while (n != 0) {
count += (n & 1);
n >>>= 1;
}
return count;
*/
/*
// 针对C/C++类语言(不区分循环右移和逻辑右移)的解法,使测试位左移即可。
int count = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0)
count++;
flag <<= 1;
}
return count;
*/
// 一个数减1,会使得这个数的最右边1变成0,而该位右边原本自然全是0,现在变成全是1;
// 然后将该数 n 与 n-1 做与运算,将会清空最右边1及其右边各位,而左边保持不变;
// 下次再进行这样的运算,又会清除最右边的1,即原数中的右数第二个1,以此类推;
// 原数中有几个1,循环执行几次,所以一般循环次数要比上述方法少,效率高。
// 例如:n = 1100. n-1 = 1011
// n & (n - 1) = 1000,count加1,n = 1000,n - 1 = 0111
// n & (n - 1) = 0. count加1,结束循环,count = 2
int count = 0;
while (n != 0) {
count++;
n &= (n - 1);
}
return count;
}
}
延申:
- 判断一个数是不是2的整数次幂。
因为2的整数次幂只有最高位是1,所以只需要判断这个数 n 与 n-1 的与运算结果是不是0就可以了。
- 输入两个整数 m 和 n ,计算需要改变 m 中的多少位才能让它变成 n。比如10(1010)和13(1101),需要改变3位。
只需要计算两个数有多少位是不一样的即可,这里可以借助异或操作——相同为0,不同为1;然后统计异或结果中 1 的个数即可。
数值的整数次方
public class Solution {
public double Power(double base, int exponent) {
// 标志结果是否需要取倒数
boolean flag = false;
double ans = 1.0;
if (Math.abs(base) < 1e-6 && exponent < 0)
throw new ArithmeticException("by zero");
// 0的0次幂,未定义。
if (Math.abs(base) < 1e-6 && exponent == 0)
return Double.NaN;
// 这里有一个问题:若exponent = Integer.MIN_VALUE,取反还是自身
if (exponent < 0) {
exponent = -exponent;
flag = true;
}
// 这里隐含溢出的问题
while (exponent != 0) {
if ((exponent & 1) == 1)
ans *= base;
base *= base;
exponent >>>= 1;
}
return flag ? 1.0 / ans : ans;
}
}
打印从1到最大的n位数
陷阱,n可能很大,超过基本类型的表示范围
public class Solution {
private int[] array;
public void printMaxToNDigits(int n) {
if (n <= 0)
return;
array = new int[n];
helper(0);
}
private void helper(int n) {
for (int i = 0; i < 10; i++) {
// 如果还没填充完,就递归填充
if (n != array.length) {
array[n] = i;
helper(n + 1);
} else {
boolean isBeginning0 = true;
for (int j = 0; j < array.length; j++) {
// 过滤前缀0
if (isBeginning0 && array[j] != 0)
isBeginning0 = false;
if (!isBeginning0)
System.out.print(array[j]);
}
System.out.println();
return;
}
}
}
}
O(1)时间内,删除链表中给定结点
给定头结点和待删除结点。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public void deleteNode(ListNode head, ListNode deleteNode) {
if (deleteNode == null || head == null)
return;
// 如果待删除结点有后续结点,为了保证O(1)复杂度,可以将后继结点数据复制到待删除结点
// 然后删除后继结点即可。
if (deleteNode.next != null) {
ListNode nextNode = deleteNode.next;
deleteNode.val = nextNode.val;
deleteNode.next = nextNode.next;
nextNode = null;
// 只有一个结点
} else if (deleteNode == head) {
head = null;
// 是尾结点,只能从前往后遍历找到前一个结点了。
} else {
ListNode temp = head;
while (temp.next != deleteNode)
temp = temp.next;
temp.next = null;
deleteNode = null;
}
}
}
删除链表中重复的结点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if (pHead == null)
return null;
// 防止头结点被删除
ListNode dummy = new ListNode(-1);
dummy.next = pHead;
// pre指向遍历结点的前结点
ListNode pre = dummy;
ListNode p = pHead;
while (p != null && p.next != null) {
// 如果重复,则往后遍历到不同的val再赋值
if (p.val == p.next.val) {
int val = p.val;
while (p != null && p.val == val)
p = p.next;
pre.next = p;
// 不重复,直接往后走
} else {
pre = p;
p = p.next;
}
}
return dummy.next;
}
}
正则表达式匹配
public class Solution {
private char[] str;
private char[] pattern;
public boolean match(char[] str, char[] pattern) {
if (str == null || pattern == null)
return false;
// 如果都只有1位,那么有两种情况匹配成功
// 1. 对应位相等
// 2. pattern[0] == '.',即可与任意字符匹配
if (str.length == 1 && pattern.length == 1)
return str[0] == pattern[0] || pattern[0] == '.';
this.str = str;
this.pattern = pattern;
return matchHelper(0, 0);
}
private boolean matchHelper(int sIndex, int pIndex) {
// 匹配完成
if (sIndex == str.length && pIndex == pattern.length)
return true;
// str未到末尾,pattern就匹配完了
if (sIndex != str.length && pIndex == pattern.length)
return false;
// 对于'*'的处理
if (pIndex + 1 < pattern.length && pattern[pIndex + 1] == '*') {
// 如果当前位匹配,分两种情况,一种是严格匹配,一种是pattern位是'.'
if ((sIndex < str.length && str[sIndex] == pattern[pIndex])
|| (sIndex < str.length && pattern[pIndex] == '.')) {
// 这里又有三种情况
// 1. '*'匹配0个字符,直接跳过pattern对应的这两位
// 2. '*'匹配1个字符,继续进行下一个状态的匹配
// 3. '*'匹配多个字符,pattern位保持不变,str后移
return matchHelper(sIndex, pIndex + 2)
|| matchHelper(sIndex + 1, pIndex + 2)
|| matchHelper(sIndex + 1, pIndex);
} else {
// 如果当前位置不匹配,此时不能直接返回false,还有一种情况:'*'匹配0个字符
// 应跳过pattern对应两位继续匹配
// 比如str[sIndex] = 'a'
// pattern[pIndex] = 'b'
// pattern[pIndex + 1] = '*'
// 此时可以理解为匹配0个'b'
return matchHelper(sIndex, pIndex + 2);
}
}
// 如果当前位置匹配,则往后推进,两种情况:
// 1. 当前pattern位为'.',且str并没有匹配完
// 2. 严格匹配
if ((pattern[pIndex] == '.' && sIndex != str.length) ||
sIndex != str.length && pattern[pIndex] == str[sIndex])
return matchHelper(sIndex + 1, pIndex + 1);
return false;
}
}
表示数值的字符串
public class Solution {
public boolean isNumeric(char[] str) {
if (str == null) {
return false;
}
int index = 0;
int eCount = 0;
int point = 0;
// 如果第一个字符是符号就跳过
if (str[0] == '-' || str[0] == '+')
index++;
for (int i = index; i < str.length; i++) {
if (str[i] == '-' || str[i] == '+') {
if (str[i - 1] != 'e' && str[i - 1] != 'E')
return false;
continue;
}
if (str[i] == 'e' || str[i] == 'E') {
eCount++;
if (eCount > 1)
return false;
if (i == 0 || str[i - 1] < 48 || str[i - 1] > 57 || i == str.length - 1)
return false;
// e后边不能有小数点,这里以point的计数来判断这种情况
point++;
continue;
}
if (str[i] == '.') {
point++;
if (point > 1)
return false;
continue;
}
// 出现非数字且不是e/E则返回false(小数点和符号用continue跳过了)
if ((str[i] < 48 || str[i] > 57) && (str[i] != 'e') && (str[i] != 'E'))
return false;
}
return true;
}
}
调整数组顺序使奇数位于偶数前边并保持相对顺序
public class Solution {
public void reOrderArray(int [] array) {
if (array == null || array.length == 0)
return;
// 插入排序的思想
for (int i = 1; i < array.length; i++) {
// 如果是奇数,则往前插
if ((array[i] & 0x1) == 1) {
int j = i - 1;
// 保存当前值
int temp = array[i];
// 遍历,直到数组开头,或者到达一个奇数为止
while (j >= 0 && (array[j] & 0x1) == 0) {
// 往后挪一位
array[j + 1] = array[j];
j--;
}
// 将原array[i]插入
array[j + 1] = temp;
}
}
}
}
如果不考虑相对顺序可以参考快排 partation 的思想:
public class Solution {
public void reOrderArray(int [] array) {
if (array == null || array.length == 0)
return;
int odd = 0;
int even = array.length - 1;
while (odd < even) {
while (odd < even && (array[odd] & 0x1) == 1)
odd++;
while (evev > odd && (array[even] & 0x1) == 0)
even--;
if (odd < even) {
int temp = array[odd];
array[odd] = array[eve];
array[even] = temp;
}
}
}
}
链表中倒数第k个结点
双指针
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindKthToTail(ListNode head,int k) {
if (head == null || k == 0)
return null;
ListNode p1 = head;
ListNode p2 = head;
for (int i = 0; i < k - 1; i++) {
// 如果当前链表结点个数够k个
if (p1.next != null)
p1 = p1.next;
// 结点个数不足k个
else
return null;
}
while (p1.next != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
}
链表中环的入口结点
双指针经典题目
判断环:两个指针会不会相遇
找入口:相遇后,其中一个放回起始点,然后相同速度移动,会相遇在入口结点。
环上结点的个数:相遇后,一个保持不动,另一个继续遍历并计数,再次相遇即可得到环内结点数。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
if (pHead == null)
return null;
ListNode slow = pHead.next;
if (slow == null)
return null;
ListNode first = slow.next;
boolean hasLoop = false;
while (first != null && slow != null) {
if (first == slow) {
hasLoop = true;
break;
}
slow = slow.next;
first = first.next;
if (first != null)
first = first.next;
}
if (!hasLoop) {
return null;
} else {
slow = pHead;
while (first != slow) {
slow = slow.next;
first = first.next;
}
return slow;
}
}
}
反转链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode reHead = null;
ListNode p = head;
ListNode pre = null;
while (p != null) {
ListNode next = p.next;
// 下个结点为空,也就是当前这个结点是最后一个结点了
if (next == null)
reHead = p;
// 掉转指针
p.next = pre;
// 往后遍历
pre = p;
p = next;
}
return reHead;
}
}
合并两个排序的链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null)
return list2;
if (list2 == null)
return list1;
ListNode head = null;
if (list1.val < list2.val) {
head = list1;
list1 = list1.next;
} else {
head = list2;
list2 = list2.next;
}
ListNode p = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
p.next = list1;
p = list1;
list1 = list1.next;
} else {
p.next = list2;
p = list2;
list2 = list2.next;
}
}
if (list1 != null)
p.next = list1;
if (list2 != null)
p.next = list2;
return head;
}
}
二叉树的镜像
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.Stack;
public class Solution {
public void Mirror(TreeNode root) {
// 递归
/*
if (root == null)
return;
if (root.left == null && root.right == null)
return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left != null)
Mirror(root.left);
if (root.right != null)
Mirror(root.right);
*/
if (root == null) {
return ;
}
Stack<TreeNode> stack = new Stack<>();
while (root != null || !stack.isEmpty()) {
while (root != null) {
// 交换左右子节点
if (root.left != null || root.right != null) {
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
}
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
}
}
对称的二叉树
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
return helper(pRoot, pRoot);
}
// 定义对称的前序遍历,先遍历右孩子,后遍历左孩子,再与传统前序遍历比较,如果都对应相等,则是对称二叉树
private boolean helper(TreeNode root1, TreeNode root2) {
// 要将对应的空结点也考虑在内
if (root1 == null && root2 == null)
return true;
if (root1 == null || root2 == null)
return false;
if (root1.val != root2.val)
return false;
return helper(root1.left, root2.right) && helper(root1.right, root2.left);
}
}
顺时针打印矩阵
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printMatrix(int [][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
return null;
int row = matrix.length;
int col = matrix[0].length;
ArrayList<Integer> result = new ArrayList<>(row * col);
int up = 0;
int down = row - 1;
int left = 0;
int right = col - 1;
while (true) {
// 从左往右
for (int i = left; i <= right; i++)
result.add(matrix[up][i]);
up++;
if (up > down)
break;
// 从上往下
for (int i = up; i <= down; i++)
result.add(matrix[i][right]);
right--;
if (left > right)
break;
// 从右往左
for (int i = right; i >= left; i--)
result.add(matrix[down][i]);
down--;
if (up > down)
break;
// 从下往上
for (int i = down; i >= up; i--)
result.add(matrix[i][left]);
left++;
if (left > right)
break;
}
return result;
}
}
包含min函数的栈
解法一:辅助栈
import java.util.Stack;
public class Solution {
private Stack<Integer> dataStack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int node) {
if (minStack.empty()) {
minStack.push(node);
} else {
int min = minStack.peek();
if (node > min)
minStack.push(min);
else
minStack.push(node);
}
dataStack.push(node);
}
public void pop() {
if (!dataStack.empty()) {
dataStack.pop();
minStack.pop();
}
}
public int top() {
if (!dataStack.empty())
return dataStack.peek();
return -1;
}
public int min() {
if (!minStack.empty())
return minStack.peek();
return -1;
}
}
解法二:只用一个栈
import java.util.Stack;
public class Solution {
private Stack<Integer> stack = new Stack<>();
private int min = Integer.MAX_VALUE;
public void push(int node) {
// 如果这个值比栈中元素都小,则将原始min压入栈,并更新min,
if (min >= node) {
stack.push(min);
min = node;
}
// 否则就直接压入,min依然保存栈中最小值
stack.push(node);
}
public void pop() {
// 如果栈顶元素等于min,说明这个值入栈的时候更新了min,它下边还压着原来的min
// 所以再把这个元素弹出的时候,也要还原原来的min
if (stack.pop() == min)
min = stack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return min;
}
}
栈的压入、弹出序列
乱七八糟写法:
import java.util.Stack;
public class Solution {
private Stack<Integer> stack = new Stack<>();
public boolean IsPopOrder(int [] pushA,int [] popA) {
if (pushA == null || popA == null || pushA.length != popA.length)
return false;
int len = pushA.length;
stack.push(pushA[0]);
int index1 = 1;
int index2 = 0;
while (index1 < len && index2 < len) {
while (stack.peek() != popA[index2] && index1 < len) {
stack.push(pushA[index1]);
index1++;
}
if (stack.peek() == popA[index2]) {
stack.pop();
index2++;
} else
return false;
}
if (index2 < len) {
while (!stack.empty() && stack.peek() == popA[index2]) {
stack.pop();
index2++;
}
}
return index2 == len;
}
}
解法二:有点单调栈的意思,匹配则往后移,不然就一直压栈
import java.util.Stack;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if (pushA == null || popA == null || pushA.length != popA.length)
return false;
Stack<Integer> stack = new Stack<>();
int index = 0;
for (int i = 0; i < pushA.length; i++) {
stack.push(pushA[i]);
while (!stack.empty() && stack.peek() == popA[index]) {
stack.pop();
index++;
}
}
return stack.empty();
}
}
从上往下打印二叉树
层序遍历
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null)
return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
result.add(node.val);
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
return result;
}
}
层序遍历的扩展——对于需要记录层数或者每层结点个数等等的问题。
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
if (root == null)
return result;
Queue<TreeNode> queue = new LinkedList<>();
// 统计下一层的结点个数,待count遍历完后赋值。
int nextLevel = 0;
// 保存当前层结点个数,在遍历中递减,减到0说明遍历完一层
int count = 1;
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
result.add(node.val);
count--;
if (node.left != null) {
queue.offer(node.left);
nextLevel++;
}
if (node.right != null) {
queue.offer(node.right);
nextLevel++;
}
if (count == 0) {
count = nextLevel;
nextLevel = 0;
}
}
return result;
}
}
按之字形顺序打印二叉树
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if (pRoot == null)
return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
int depth = 1;
while (!queue.isEmpty()) {
LinkedList<Integer> temp = new LinkedList<>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
// 奇数层,从左往右
if ((depth & 0x1) != 0)
temp.offerLast(node.val);
else
temp.offerFirst(node.val);
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
result.add(new ArrayList(temp));
depth++;
}
return result;
}
}
二叉搜素树的后序遍历序列
public class Solution {
private int[] sequence;
public boolean VerifySquenceOfBST(int [] sequence) {
if (sequence == null || sequence.length == 0)
return false;
this.sequence = sequence;
return helper(0, sequence.length - 1);
}
private boolean helper(int left, int right) {
if (left > right)
return false;
int root = sequence[right];
int i = 0;
// 寻找左子树范围,循环终止于第一个大于根结点的结点,即右子树第一个结点
for (; i < right; i++) {
if (sequence[i] > root)
break;
}
int j = i;
for (; j < right; j++) {
// 如果右子树有小于根节点的,则不符合二叉搜索树要求,返回false
if (sequence[j] < root)
return false;
}
// 如果存在左子树,则判断左子树
boolean l = true;
if (i > 0)
l = helper(0, i - 1);
// 如果存在右子树,则判断右子树
boolean r = true;
if (i < right)
r = helper(i, right - 1);
return l && r;
}
}
二叉树中和为某一值的路径
import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private int target;
private Stack<Integer> stack = new Stack<>();
private ArrayList<ArrayList<Integer>> result;
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
result = new ArrayList<>();
if (root == null)
return result;
this.target = target;
helper(root, 0);
return result;
}
private void helper(TreeNode root, int sum) {
sum += root.val;
stack.push(root.val);
// 剪枝,如果当前结果已经大于预期值则直接返回
// 注意,如果结点值存在复制,则不能有这个判断
if (sum > target)
return;
boolean isLeaf = root.left == null && root.right == null;
// 这里的返回同样基于结点值非负的前提,否则不能返回,因为后续可能有负值
if (sum == target) {
if (isLeaf)
result.add(new ArrayList(stack));
else
return;
}
if (root.left != null)
helper(root.left, sum);
if (root.right != null)
helper(root.right, sum);
// 判断完当前当前结点的子结点后,需要回溯,要将当前结点从路径中删掉
stack.pop();
}
}
复杂链表的复制
解法一:暴力,先以next字段构造好链表,然后再处理random字段。对于原始链表中的每一个random,都在新链表中从头遍历寻找。平方复杂度
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
RandomListNode head = new RandomListNode(pHead.label);
RandomListNode p = head;
RandomListNode q = pHead.next;
// 以next字段复制链表
while (q != null) {
RandomListNode temp = new RandomListNode(q.label);
p.next = temp;
p = temp;
q = q.next;
}
// 处理random字段,齐头并进
q = pHead;
RandomListNode r = head;
while (q != null) {
if (q.random != null) {
// 对于当前结点的random,在链表中遍历label等于该值的结点
int val = q.random.label;
p = head;
while (p != null && p.label != val)
p = p.next;
r.random = p;
} else {
r.random = null;
}
// 同步后移
r = r.next;
q = q.next;
}
return head;
}
}
解法二:哈希表,以空间换时间。时间、空间复杂度都为O(n)
import java.util.*;
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
RandomListNode head = new RandomListNode(pHead.label);
Map<RandomListNode, RandomListNode> map = new HashMap<>();
RandomListNode p = head;
map.put(pHead, head);
RandomListNode q = pHead.next;
while (q != null) {
RandomListNode temp = new RandomListNode(q.label);
p.next = temp;
p = temp;
map.put(q, p);
q = q.next;
}
p = head;
q = pHead;
while (q != null) {
p.random = map.get(q.random);
p = p.next;
q = q.next;
}
return head;
}
}
解法三:新旧链表合一,借用原来的指针关系设置新链表的random字段,未使用额外空间,时间复杂度为O(n)
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null)
return null;
RandomListNode head = new RandomListNode(pHead.label);
// 第一步
// 逐个复制,并把当前结点插入到原链表对应结点的后边
RandomListNode q = pHead.next;
pHead.next = head;
head.next = q;
while (q != null) {
RandomListNode temp = new RandomListNode(q.label);
temp.next = q.next;
q.next = temp;
q = temp.next;
}
// 第二步,处理random
// 由于每个新结点都在每个旧结点的后边,所以新结点的random,就在对应旧结点的random的next处
q = pHead;
while (q != null) {
// 这里防止空指针异常
if (q.random != null)
q.next.random = q.random.next;
// q只遍历旧结点,跳过下一个对应的新结点
// 此外,由于每个结点都有对应的新结点在其后边,所以这里不会出现空指针异常
q = q.next.next;
}
// 第三步,新旧链表拆分
q = pHead;
while (q != null) {
// 该结点对应的新结点
RandomListNode cloned = q.next;
// 原始链表中的下一个结点
RandomListNode src = cloned.next;
// 将原始链表的next字段指回原来的结点
q.next = src;
// 将新结点的next字段指向下一个新结点
if (src != null)
cloned.next = src.next;
// 原始结点向后遍历
q = src;
}
return head;
}
}
二叉搜索树与双向链表
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return null;
TreeNode last = helper(pRootOfTree, null);
while (last.left != null) {
last = last.left;
}
return last;
}
// 传入当前结点和这个结点前排好序的最后一个结点
// 基于中序遍历
private TreeNode helper(TreeNode cur, TreeNode last) {
if (cur == null)
return null;
// 递归处理左子树
if (cur.left != null)
last = helper(cur.left, last);
// 处理当前结点,将当前结点与左子树最后一个结点相连接
cur.left = last;
if (last != null)
last.right = cur;
// 更新最后一个结点为cur,因为cur此时是处理完的最后一个结点
last = cur;
// 递归处理右子树
if (cur.right != null)
last = helper(cur.right, last);
return last;
}
}
序列化二叉树
还是前序遍历的思想
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
String Serialize(TreeNode root) {
if (root == null) {
return "";
}
StringBuilder sb = new StringBuilder();
helpSerialize(root, sb);
return sb.toString();
}
private void helpSerialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append("#!");
return;
}
sb.append(root.val).append("!");
if (root.left != null) {
helpSerialize(root.left, sb);
} else {
sb.append("#!");
}
if (root.right != null) {
helpSerialize(root.right, sb);
} else {
sb.append("#!");
}
}
private int index = 0;
private String[] split;
TreeNode Deserialize(String str) {
if (str == null || str.length() == 0)
return null;
this.split = str.split("!");
return helpDeserialize();
}
private TreeNode helpDeserialize() {
if (split[index].equals("#")) {
index++;
return null;
}
TreeNode node = new TreeNode(Integer.valueOf(split[index]));
index++;
node.left = helpDeserialize();
node.right = helpDeserialize();
return node;
}
}
public class Serializer62 {
public static String serialize(TreeNode root) {
StringBuffer sb = new StringBuffer();
if (root == null) {
sb.append("#,");
return sb.toString();
}
sb.append(root.val + ",");
sb.append(serialize(root.left));
sb.append(serialize(root.right));
return sb.toString();
}
private static int index = -1;
public static TreeNode deserialize(String str) {
index++;
int len = str.length();
String[] strArray = str.split(",");
TreeNode node = null;
if (index >= len) {
return null;
}
if (!strArray[index].equals("#")) {
node = new TreeNode(Integer.valueOf(strArray[index]));
node.left = deserialize(str);
node.right = deserialize(str);
}
return node;
}
}
字符串的排列
import java.util.ArrayList;
import java.util.TreeSet;
public class Solution {
private char[] chars;
private TreeSet<String> set;
private int len;
public ArrayList<String> Permutation(String str) {
if (str == null || str.length() == 0)
return new ArrayList<String>();
set = new TreeSet<>();
this.chars = str.toCharArray();
this.len = chars.length;
helper(0);
return new ArrayList<String>(set);
}
private void helper(int index) {
if (index == len) {
set.add(new String(chars));
return;
}
for (int i = index; i < len; i++) {
char c = chars[index];
chars[index] = chars[i];
chars[i] = c;
helper(index + 1);
c = chars[index];
chars[index] = chars[i];
chars[i] = c;
}
}
}
数组中出现次数超过一半的数字
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if (array == null || array.length == 0)
return 0;
int count = 1;
int data = array[0];
for (int i = 1; i < array.length; i++) {
if (count == 0) {
data = array[i];
count = 1;
} else if (array[i] == data) {
count++;
} else {
count--;
}
}
if (count > 0) {
count = 0;
for (int i : array) {
if (i == data)
count++;
}
if (count > array.length / 2)
return data;
}
return 0;
}
}
快排思想
public class Solution {
private int[] array;
public int MoreThanHalfNum_Solution(int [] array) {
if (array == null || array.length == 0)
return 0;
this.array = array;
int middle = array.length >>> 1;
int left = 0;
int right = array.length - 1;
int index = partation(left, right);
while (index != middle) {
if (index > middle)
right = index - 1;
else
left = index + 1;
index = partation(left, right);
}
int count = 0;
for (int i : array) {
if (i == array[index])
count++;
}
if (count > array.length / 2)
return array[index];
else
return 0;
}
private int partation(int left, int right) {
int temp = array[left];
int begin = left;
while (left < right) {
while (left < right && array[left] <= temp)
left++;
//array[right] = array[left];
while (left < right && array[right] > temp)
right--;
//array[left] = array[right];
if (left < right) {
int i = array[left];
array[left] = array[right];
array[right] = i;
left++;
right--;
}
}
array[begin] = array[left];
array[left] = temp;
return left;
}
// 挖坑法
private int partation2(int left, int right) {
int temp = array[right];
while (left < right) {
while (left < right && array[left] <= temp)
left++;
array[right] = array[left];
while (left < right && array[right] > temp)
right--;
array[left] = array[right];
}
array[left] = temp;
return left;
}
}
最小的K个数
解法一:快排的partation思想
解法二:大根堆
import java.util.PriorityQueue;
import java.util.Comparator;
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
if (input == null || input.length == 0 || k <= 0 || k > input.length)
return new ArrayList<Integer>();
// 大根堆
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2 - i1;
}
});
// 如果堆中元素不足k个,则直接插入,否则比较堆顶元素,即最大元素,如果大于当前元素,则替换
for (int i = 0; i < input.length; i++) {
if (pq.size() < k)
pq.offer(input[i]);
else if (pq.peek() > input[i]) {
pq.poll();
pq.offer(input[i]);
}
}
return new ArrayList<Integer>(pq);
}
}
数据流中的中位数
import java.util.PriorityQueue;
public class Solution {
// 大根堆存储左边元素
private PriorityQueue<Integer> left = new PriorityQueue<>((i1, i2) -> i2 - i1);
// 小根堆存储右边元素
private PriorityQueue<Integer> right = new PriorityQueue<>();
// 统计数据流中的元素
private int N = 0;
public void Insert(Integer num) {
// 数据流中有偶数个数,那么可以把这个数放入右边
// 不过需要先和左边最大值比较,放较大的数
if ((N & 0x1) == 0) {
if (!left.isEmpty() && num >= left.peek())
right.offer(num);
else {
left.offer(num);
right.offer(left.poll());
}
// 如果当前有奇数个数,那么就是右边比左边多一个
// 为了保持平衡,需要把这个数放左边,同样也需要和右边最小值比较
} else {
if (!right.isEmpty() && num < right.peek())
left.offer(num);
else {
right.offer(num);
left.offer(right.poll());
}
}
N++;
}
public Double GetMedian() {
if ((N & 0x1) == 0)
return Double.valueOf((left.peek() + right.peek()) / 2.0);
else
return (double)right.peek();
}
}
连续子数组的最大和
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
if (array == null || array.length == 0)
return -1;
int[] dp = new int[array.length];
dp[0] = array[0];
for (int i = 1; i < array.length; i++) {
dp[i] = Math.max(array[i], dp[i - 1] + array[i]);
}
int max = dp[0];
for (int i = 1; i < dp.length; i++) {
if (dp[i] > max)
max = dp[i];
}
return max;
}
}
整数中1出现的次数
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;
//思路是分别计算个位、十位、百位........上出现 1 的个数。
// 分左右两边计算
// 如果 a 的个位大于1,那么考虑左边会有 (a / 10 + 1) 个,该个位是1的,因为当前是以i为单位,每个数后边的低位还有整i个,所以再乘以i,
// 如果 a 个位是1,按理说,左边也该有(a / 10 + 1)个, 但是此时右边是半截,只有b个数,并不满足 i 个,所以要单独考虑,加8是为了保证大于等于2的时候才会进1
//以 n =216为例:
//个位上: 01 ,11,21,31,.....211。个位上共出现(216/10)+ 1个 1 。因为除法取整,210~216间个位上的1取不到,所以加8进位。n=211怎么办,这里把最后取到的个位数为1的单独考虑,先往下看。
//十位上:10~19,110~119,210~216. 十位上可看成 求(216/10)=21 个位上的1的个数然后乘10。这里再次把最后取到的十位数为1的单独拿出来,即210~216要单独考虑 ,个数为(216%10)+1 .这里加8就避免了判断的过程。
//后面以此类推。
//时间复杂度 O(logN)
for (int i = 1; i <= n; i *= 10) {
// a 取n的高位
int a = n / i;
// b 取低位
int b = n % i;
count += (a + 8) / 10 * i;
if ((a % 10 == 1))
count += b + 1;
}
return count;
}
}
把数组排成最小的数
import java.util.*;
import java.util.stream.*;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if (numbers == null || numbers.length == 0)
return "";
/*
String[] str = new String[numbers.length];
// 转为字符串
for (int i = 0; i < numbers.length; i++) {
str[i] = String.valueOf(numbers[i]);
}
// 比较两个数的大小,就看这两个数排在一起组成的字符串的大小,并决定谁前谁后
Arrays.sort(str, (s1, s2) -> (s1 + s2).compareTo(s2 + s1));
StringBuilder sb = new StringBuilder();
for (String s : str)
sb.append(s);
return sb.toString();
*/
return IntStream.of(numbers)
.mapToObj(i -> String.valueOf(i))
.sorted((s1, s2) -> (s1 + s2).compareTo(s2 + s1))
.collect(Collectors.joining());
}
}
把数字翻译成字符串
public class Solution {
public int numDecodings(String s) {
if (s == null || s.length() == 0)
return 0;
if (s.charAt(0) == '0')
return 0;
int len = s.length();
int[] dp = new int[len + 1];
// 类似于上楼梯,每个位置的次数都由前两个位置的次数决定
dp[0] = 1;
for (int i = 0; i < len; i++) {
// 如果当前位为0,那么不能从i走,只能从i - 1处到i + 1处,所以这里不加i的记录
// 如果当前位不是0,那么是可能从i走,也可能从i - 1走,这里先把从i走的加上
dp[i + 1] = s.charAt(i) == '0' ? 0 : dp[i];
// 如果满足两位的情况,那么可能从当前i走到i + 1,也可能从i - 1走到i + 1
// 所以这里要再加上i - 1处的记录
if (i > 0 && (s.charAt(i - 1) == '1'
||(s.charAt(i - 1) == '2' && s.charAt(i) <= '6'))) {
dp[i + 1] += dp[i - 1];
}
}
return dp[len];
}
}
礼物的最大值
import java.util.*;
public class Bonus {
public int getMost(int[][] board) {
// write code here
if (board == null || board.length == 0 || board[0].length == 0)
return 0;
int row = board.length;
int col = board[0].length;
int[][] dp = new int[row][col];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int left = 0, up = 0;
if (i > 0)
up = dp[i - 1][j];
if (j > 0)
left = dp[i][j - 1];
dp[i][j] = Math.max(left, up) + board[i][j];
}
}
return dp[row - 1][col - 1];
}
}
优化:
import java.util.*;
public class Bonus {
public int getMost(int[][] board) {
// write code here
if (board == null || board.length == 0 || board[0].length == 0)
return 0;
int row = board.length;
int col = board[0].length;
int[] dp = new int[col];
// 例如:第0行的时候,一行扫描完后,dp[j]记录的是当前行到达这个位置礼物的最大值,因为只有left会根据上一个位置更新,up一直是0
// 第1行的时候,首先up=dp[0],也就是上一行扫描到此得到的最大礼物值,然后dp[0]会更新,即再加上当前位置的值
// 然后随着j往后走,每一个位置的up都等于dp[j],因为还没更新dp[j],它还是保存的上一行这个位置的最大价值
// 而left变了,left=dp[j - 1],dp[j - 1]在上一次已经修改,就是j - 1处的礼物最大值,也就是所需的当前位置的left。
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
int left = 0, up = 0;
// 此时dp[j]表示的是上一行扫描到这个列时候,礼物的最大值
if (i > 0)
up = dp[j];
// 此时dp[j - 1]表示的是目前按列扫描,当前位置左边位置礼物的最大值
if (j > 0)
left = dp[j - 1];
// 按列扫描的时候,这里得到的dp[j]表示走到[i][j]最大价值
dp[j] = Math.max(left, up) + board[i][j];
}
}
return dp[col - 1];
}
}
最长不含重复字符的子字符串
动态规划:f(i)表示到位置i满足要求的最长的子字符串
1. 如果当前位置字符没出现过,那么长度加1,f(i) = f(i - 1) + 1
2. 如果当前这个字符之前出现过了
2.1 判断两次出现的距离 d(所以这里需要记录每个字母上次出现的位置),如果d 小于等于f(i - 1),说明上个字符是参与在f(i - 1)中的,那么当前位置又出现就打断了之前的字符串,
只能把上次出现的位置以前的丢弃,但是中间夹着的是不重复的,所以f(i) = d
2.2 如果两次出现的距离 d 大于f(i - 1),说明这个字符虽然之前出现过,但是并没有参与在f(i - 1)的计算中,并不在它所指的子串里,所以这里这个元素对于上个位置的最长字串也是第一次,直接f(i) = f(i - 1) + 1即可。
public class Solution {
public int longestSubstringWithoutDuplication(String s) {
if (s == null || s.length() == 0)
return 0;
int cur = 0;
int max = 0;
// 记录字符上次出现的位置
int[] position = new int[26];
Arrays.fill(position, -1);
for (int i = 0; i < s.length(); i++) {
int index = s.charAt(i) - 'a';
int preIndex = position[index];
// 当前元素没出现过,或者距离很远,不在当前记录的子串中
if (preIndex < 0 || i - preIndex > cur)
cur++;
else {
// 循环里只需要在这里判断下就行了
if (cur > max)
max = cur;
// 将当前长度更新为d
cur = i - preIndex;
}
// 记录或更新当前元素出现的位置
position[index];
}
// 不需要在循环里每次判断,循环里只需要保证在将cur改为d的时候,和最大值比较就行了
if (cur > max)
max = cur;
return max;
}
}
丑数(只含2、3、5因子的数字)
public class Solution {
public int GetUglyNumber_Solution(int index) {
// 1-6 分别对应前6个丑数
if (index <= 6)
return index;
// 记录经过计算可能会成为下一丑数的丑数,该丑数已经记录在当前数组
int i2 = 0, i3 = 0, i5 = 0;
// 数组只保存丑数
int[] dp = new int[index];
dp[0] = 1;
for (int i = 1; i < index; i++) {
// 乘以2可能会成为下个丑数的值。3、5类似
int next2 = dp[i2] * 2;
int next3 = dp[i3] * 3;
int next5 = dp[i5] * 5;
// 取三者最小值
dp[i] = Math.min(next2, Math.min(next3, next5));
// 如果最终取出来是i2对应的那个数,那么i2可以很安全地加1,因为这个数乘2的值已经被记录在dp[i]了。
// 3、5类似
if (dp[i] == next2)
i2++;
if (dp[i] == next3)
i3++;
if (dp[i] == next5)
i5++;
}
return dp[index - 1];
}
}
第一个只出现一次的字符
public class Solution {
public int FirstNotRepeatingChar(String str) {
if (str == null || str.length() == 0)
return -1;
int[] chars = new int[256];
for (int i = 0; i < str.length(); i++) {
chars[str.charAt(i)]++;
}
for (int i = 0; i < str.length(); i++) {
if (chars[str.charAt(i)] == 1)
return i;
}
return -1;
}
}
字符流中第一个不重复的字符
import java.util.*;
public class Solution {
private int[] chars = new int[256];
private Queue<Character> queue = new LinkedList<>();
//Insert one char from stringstream
public void Insert(char ch)
{
chars[ch]++;
queue.offer(ch);
while (!queue.isEmpty() && chars[queue.peek()] > 1)
queue.poll();
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()
{
return queue.isEmpty() ? '#' : queue.peek();
}
}
数组的逆序对数
public class Solution {
private long count = 0;
private int[] temp;
public int InversePairs(int [] array) {
if (array == null || array.length == 0)
return 0;
temp = new int[array.length];
mergeSort(array, 0, array.length - 1);
return (int)(count % 1_000_000_007);
}
private void mergeSort(int[] array, int left, int right) {
if (left + 1 > right) {
return;
}
int mid = (left + right) >>> 1;
mergeSort(array, left, mid);
mergeSort(array, mid + 1, right);
merge(array, left, mid, right);
}
private void merge(int[] array, int left, int mid, int right) {
// i, j指向待归并的两个数组的起始位置,k指向辅助数组的位置
int i = left, k = left, j = mid + 1;
while (i <= mid && j <= right) {
// 不存在逆序
if (array[i] <= array[j]) {
temp[k] = array[i++];
} else {
temp[k] = array[j++];
// 因为子数组是有序的,所以如果array[i] > array[j],那么左半边数组剩下的值都比array[j]大
this.count += mid - i + 1;
}
k++;
}
// 如果存在某个数组还有剩余,那么按序放入temp即可
while (i <= mid)
temp[k++] = array[i++];
while (j <= right)
temp[k++] = array[j++];
// 将排好序的数组复制回原数组
for (k = left; k <= right; k++)
array[k] = temp[k];
}
}
两个链表的第一个公共结点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null)
return null;
ListNode p1 = pHead1;
ListNode p2 = pHead2;
// 计算两个链表长度
int len1 = 0, len2 = 0;
while (p1 != null) {
len1++;
p1 = p1.next;
}
while (p2 != null) {
len2++;
p2 = p2.next;
}
// 让较长的链表先往后走,保证两者同步
int d = Math.abs(len1 - len2);
p1 = pHead1;
p2 = pHead2;
if (len1 <= len2) {
while (d != 0) {
d--;
p2 = p2.next;
}
} else {
while (d != 0) {
d--;
p1 = p1.next;
}
}
while (p1 != null && p2 != null) {
// 找到公共结点直接返回
if (p1 == p2)
return p1;
p1 = p1.next;
p2 = p2.next;
}
return null;
}
}
数字在排序数组中出现的次数
解法一:
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if (array == null || array.length == 0)
return 0;
int first = binarySearchFirst(array, k);
int last = binarySearchLast(array, k);
if (first != -1 && last != -1)
return last - first + 1;
return 0;
}
// 寻找左边界
private int binarySearchFirst(int[] array, int k) {
int left = 0;
int right = array.length;
while (left < right) {
int mid = (left + right) >>> 1;
if (array[mid] > k)
right = mid;
else if (array[mid] < k)
left = mid + 1;
else
right = mid;
}
if (left == array.length)
return -1;
return array[left] == k ? left : -1;
}
// 寻找右边界
private int binarySearchLast(int[] array, int k) {
int left = 0;
int right = array.length;
while (left < right) {
int mid = (left + right) >>> 1;
if (array[mid] > k)
right = mid;
else if (array[mid] < k)
left = mid + 1;
else
left = mid + 1;
}
return left - 1;
}
}
解法二:只定义一个寻找左边界的二分查找,右边界可以以查找K + 1代替(比K大的数,它的左边界肯定是在K的右边界的下一个位置),不过这里注意不能在二分查找里排除没查找到的情况,要如实返回最终所有的位置,因为搜索K + 1是为了要最终搜索的位置而不管搜没搜到。
public int GetNumberOfK(int[] nums, int K) {
int first = binarySearch(nums, K);
int last = binarySearch(nums, K + 1);
// 因为改造的二分法并不保证最终返回的结果是不是K,所以要在这里判断
// 这里的判断对应上边解法一寻找左边界的判断。
return (first == nums.length || nums[first] != K) ? 0 : last - first;
}
private int binarySearch(int[] nums, int K) {
int left = 0, right = nums.length;
while (left < right) {
int mid = (left + right) >>> 1;
if (nums[mid] >= K)
right = mid;
else
left = mid + 1;
}
return left;
}
二叉搜索树的第K个结点
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private int count = 0;
private TreeNode result;
TreeNode KthNode(TreeNode pRoot, int k) {
if (pRoot == null || k == 0)
return null;
helper(pRoot, k);
return result;
}
private void helper(TreeNode root, int k) {
if (root == null || count >= k)
return;
helper(root.left, k);
count++;
if (count == k) {
result = root;
return;
}
helper(root.right, k);
}
}
二叉树的深度
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public int TreeDepth(TreeNode root) {
if (root == null)
return 0;
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return Math.max(left, right) + 1;
}
}
数组中出现一次的数字
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
// 思路来源于数组中只有一个数字出现一次,其他出现两次,那么将数组元素逐个异或,最终就得到了所求的那个数字
// 因为出现两次的异或结果为0
// 本题基于这个思想,先对数组元素逐个异或
// 异或结果等于两个所求数字的异或结果,必不为0
// 寻找异或结果中的最右边1,那么这两个数字在该位上肯定是不同的,所以异或结果才能是1
// 以此位为掩码,可以将数组一分为二,一个该位为0,另一个该位为1,那么所求的两个数必然被分到两个数组
// 而相同的数肯定位于同一个数组,所以问题回到在数组中寻找一个出现一次的数,逐个异或即可
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int temp = 0;
for (int num : array) {
temp ^= num;
}
/*
int bit = 1;
while ((temp & bit) == 0)
bit <<= 1;
*/
int bit = temp & (-temp);
for (int num : array) {
if ((num & bit) != 0)
num1[0] ^= num;
else
num2[0] ^= num;
}
}
}
数组中唯一只出现一次的数字,其他数字出现3次
沿用位运算,计算 int
型每一位的和,如果该位能被3整除,说明所求的数这一位是0,否则是1。
因为对于其他出现3次的数,每一位的和都是能被3整除的。
public class Solution {
public int findNumAppearOnce(int[] array) {
if (array == null || array.length == 0)
return -1;
int[] bitSum = new int[32];
for (int i = 0; i < array.length; i++) {
int mask = 1;
for (int j = 31; j >= 0; j--) {
int bit = array[i] & mask;
if (bit != 0)
bitSum[j] += 1;
mask <<= 1;
}
}
int result = 0;
for (int i = 0; i < 32; i++) {
result <<= 1;
result += bitSum[i] % 3;
}
return result;
}
}
和为sum的两个数字
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList<Integer> result = new ArrayList<>();
if (array == null || array.length == 0)
return result;
int left = 0;
int right = array.length - 1;
while (left < right) {
int s = array[left] + array[right];
if (s > sum)
right--;
else if (s < sum)
left++;
else {
result.add(array[left]);
result.add(array[right]);
break;
}
}
return result;
}
}
和为S的连续正数序列
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if (sum <= 0)
return result;
int small = 1;
int big = 2;
int curSum = small + big;
int mid = (1 + sum) >>> 1;
while (small < mid) {
if (curSum < sum) {
big++;
curSum += big;
} else if (curSum > sum) {
curSum -= small;
small++;
} else {
ArrayList<Integer> temp = new ArrayList<>();
for (int i = small; i <= big; i++)
temp.add(i);
result.add(temp);
big++;
curSum += big;
}
}
return result;
}
}
反转单词顺序列
public class Solution {
// 整体反转,逐个单词反转
public String ReverseSentence(String str) {
if (str == null || str.length() <= 1)
return str;
char[] ch = str.toCharArray();
int len = ch.length;
reverse(ch, 0, len - 1);
// 记录一个单词的左右边界
int i = 0, j = 0;
while (j <= len) {
// 如果遇到空格,或者达到字符串末尾,则表示一个单词结束,开始反转
if (j == len || ch[j] == ' ') {
reverse(ch, i, j - 1);
i = j + 1;
}
j++;
}
return new String(ch);
}
private void reverse(char[] ch, int left, int right) {
while (left < right) {
char c = ch[left];
ch[left] = ch[right];
ch[right] = c;
left++;
right--;
}
}
}
左旋转字符串
public class Solution {
public String LeftRotateString(String str,int n) {
if (str == null || str.length() <= 1)
return str;
char[] ch = str.toCharArray();
int len = ch.length;
n %= len;
n = (n + len) % len;
reverse(ch, 0, n - 1);
reverse(ch, n, len - 1);
reverse(ch, 0, len - 1);
return new String(ch);
}
private void reverse(char[] ch, int left, int right) {
while (left < right) {
char c = ch[left];
ch[left] = ch[right];
ch[right] = c;
left++;
right--;
}
}
}
滑动窗口的最大值
import java.util.ArrayList;
import java.util.PriorityQueue;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size) {
ArrayList<Integer> result = new ArrayList<>();
if (num == null || num.length == 0 || size <= 0 || size > num.length)
return result;
PriorityQueue<Integer> heap = new PriorityQueue<>((o1, o2) -> o2 - o1); /* 大顶堆 */
for (int i = 0; i < size; i++)
heap.add(num[i]);
result.add(heap.peek());
for (int i = 0, j = i + size; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */
heap.remove(num[i]);
heap.add(num[j]);
result.add(heap.peek());
}
return result;
}
}
n个骰子的点数
解法一:动态规划,二维数组,dp[i][j]表示前i个骰子掷出点数为j的次数
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
final int face = 6;
final int pointNum = face * n;
long[][] dp = new long[n + 1][pointNum + 1];
// 第一个骰子掷出各个点数的次数均为1
for (int i = 1; i <= face; i++)
dp[1][i] = 1;
// 骰子数
for (int i = 2; i <= n; i++) {
// j表示投掷点数,使用 i 个骰子最小点数为 i
for (int j = i; j <= pointNum; j++) {
// 状态转移方程,k 表示本轮投掷点数,注意 k 要同时小于 face 和 j。
for (int k = 1; k <= face && k <= j; k++)
dp[i][j] += dp[i - 1][j - k];
}
}
final double totalNum = Math.pow(6, n);
List<Map.Entry<Integer, Double>> ret = new ArrayList<>();
for (int i = n; i <= pointNum; i++)
ret.add(new AbstractMap.SimpleEntry<>(i, dp[n][i] / totalNum));
return ret;
}
解法一改进:动态规划+旋转数组。注意到解法一虽然dp数组有 n 行,但是每次都只用了前一行的数据,
随意可以改进dp数组,设置旋转标记,来回使用这两行。将空间复杂度从 O(n^2) 降为 O(n)。
public List<Map.Entry<Integer, Double>> dicesSum(int n) {
final int face = 6;
final int pointNum = face * n;
long[][] dp = new long[2][pointNum + 1];
for (int i = 1; i <= face; i++)
dp[0][i] = 1;
// 旋转标记
int flag = 1;
// 注意这里循环也改变了 flag
for (int i = 2; i <= n; i++, flag = 1 - flag) {
// 旋转数组清零
for (int j = 0; j <= pointNum; j++)
dp[flag][j] = 0;
for (int j = i; j <= pointNum; j++)
for (int k = 1; k <= face && k <= j; k++)
dp[flag][j] += dp[1 - flag][j - k];
}
final double totalNum = Math.pow(6, n);
List<Map.Entry<Integer, Double>> ret = new ArrayList<>();
for (int i = n; i <= pointNum; i++)
ret.add(new AbstractMap.SimpleEntry<>(i, dp[1 - flag][i] / totalNum));
return ret;
}
构建乘积数组
B[0] 1 A[1] … A[n]
B[1] A[0] 1 … A[n]
.
.
B[n] A[0] A[1] … 1
构成矩阵,主对角线为1,下边第一个循环计算左下角元素,第二个循环计算右上角
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
if (A == null || A.length == 0)
return A;
int len = A.length;
int[] B = new int[len];
// B[0]没有左下角元素,所以赋值1
B[0] = 1;
// 借用上一个B的计算结果来累乘这个B
for (int i = 1; i < len; i++) {
B[i] = B[i - 1] * A[i - 1];
}
// 从右下角往左上角计算
int temp = 1;
// B[len - 1]没有右边元素,所以temp初始化为 1
for (int i = len - 1; i >= 0; i--) {
B[i] = B[i] * temp;
// temp 统计右边元素
temp *= A[i];
}
return B;
}
}
扑克牌顺子
import java.util.Arrays;
public class Solution {
public boolean isContinuous(int [] numbers) {
if (numbers.length < 5)
return false;
Arrays.sort(numbers);
// 统计癞子数量
int cnt = 0;
for (int num : numbers)
if (num == 0)
cnt++;
// 使用癞子去补全不连续的顺子
for (int i = cnt; i < numbers.length - 1; i++) {
// 有对子直接返回 false
if (numbers[i + 1] == numbers[i])
return false;
// 用癞子补空缺
cnt -= numbers[i + 1] - numbers[i] - 1;
}
return cnt >= 0;
}
}
约瑟夫环
f(n, m) = (f(n - 1, m) + m) % n;
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if(n < 1 || m < 1) return -1;
int s = 0;
for(int i = 2; i <= n; i++){
s = (s + m) % i;
}
return s;
}
}
不用加减乘除做加法
public class Solution {
public int Add(int num1,int num2) {
// 直到没有进位
while(num2 != 0){
// 异或计算诸位相加的结果,没有进位
int sum = num1 ^ num2;
// 与运算计算进位,因为只有1&1=1,即产生进位,然后左移1位
int carray = (num1 & num2) << 1;
num1 = sum;
num2 = carray;
}
return num1;
}
}
把二叉树打印成多行
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if (pRoot == null)
return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
while (!queue.isEmpty()) {
int count = queue.size();
ArrayList<Integer> temp = new ArrayList<>();
while (count-- > 0) {
TreeNode node = queue.poll();
temp.add(node.val);
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
result.add(temp);
}
return result;
}
}
股票的最大利润
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
int soFarMin = prices[0];
int maxProfit = 0;
for (int i = 1; i < prices.length; i++) {
soFarMin = Math.min(soFarMin, prices[i]);
maxProfit = Math.max(maxProfit, prices[i] - soFarMin);
}
return maxProfit;
}
加法
计算1+2+…+n,不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
利用逻辑与 &&
的短路原则,将递归结束条件 n > 0
放在前边,递归计算放在后边。因为是用于逻辑与,所以要将后边的递归计算变为逻辑值。
public class Solution {
public int Sum_Solution(int n) {
int sum = n;
boolean flag = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
return sum;
}
}