【手把手教你】LeetCode力扣剑指offer算法题解-全!
写在最先
- 算法题是必备的技能之一,记录本人刷题LeetCode剑指offer的过程,希望对大家有所帮助。
- 本文是剑指offer第2版【顺序版】,后续也会整理【归类版】,敬请期待嘿嘿!!
1.找出数组中重复的数字
凡是“存在”之类的都可以试试hashset。
/**
* 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
*/
public class NumbersInArray {
public int findRepeatNumber(int[] nums) {
//1、使用hashset,慢
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
if (!set.contains(num)) {
set.add(num);
} else {
return num;
}
}
// 2.排序,快
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i-1]){
return nums[i];
}
}
return -1;
}
}
2.二维数组中的查找
因为每行都是有序的,所以边界容易判断
/**
* title:二维数组中查找
* 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
* 请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
**/
public class TwoArraySearch {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix.length==0||matrix[0].length==0){
return false;
}
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] > target){
break;
}
if (matrix[i][j] == target){
return true;
}
}
}
return false;
}
}
3.替换空格
使用replace方法
/**
* title:替换空格
*/
public class replaceNull {
public String replaceSpace(String s) {
// 方法一
return s.replace(" ","%20");
//方法二
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == ' ') {
sb.append("%20");
} else {
sb.append(c);
}
}
return sb.toString();
}
}
4.从尾到头打印链表
使用栈的特性解决,Java中栈就是arraylist
/**
* title:从尾到头打印链表
* 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
*/
public class PrintList {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
//方法一:
ArrayList<Integer> tmp = new ArrayList<Integer>();
public int[] reversePrint(ListNode head) {
recur(head);
int[] res = new int[tmp.size()];
for (int i = 0; i < res.length; i++)
res[i] = tmp.get(i);
return res;
}
void recur(ListNode head) {
if (head == null) return;
recur(head.next);
tmp.add(head.val);
}
//-------------------------------
//方法二
public int[] reversePrint(ListNode head) {
ListNode node = head;
int count = 0;
while (node!=null){
count++;
node = node.next;
}
int[] res = new int[count];
node = head;
for (int i = count - 1; i >= 0; i--) {
res[i] = node.val;
node = node.next;
}
return res;
}
}
5.重建二叉树
- 注意我们是根据前序遍历中的第一个值在中序遍历位置来判断此时root根节点左右两个子树的个数。
- 注意判断左右子树是否存在,否则范围会出现ArrayIndexOutOfBoundsException错误,两种方式均可。
- 这是一个大范围数据转化成小范围,也就是大问题转化成了本质相同的小问题,那么通常是用递归实现
/**
* title:重建二叉树
* 输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
* 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
*/
public class RestoreTree {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
//方法一:
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || inorder == null ||
inorder.length == 0 || preorder.length != inorder.length) {
return null;
}
return construction(preorder, 0, preorder.length - 1,
inorder, 0, inorder.length - 1);
}
private TreeNode construction(int[] pre, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
// 建立当前节点pre[preStart]
TreeNode root = new TreeNode(pre[preStart]);
if (preStart == preEnd) {
return root;
}
// 根据pre[preStart]查找in中数据位置记作数据A,
// A前面的元素个数用leftNum记录
int leftNum = 0;
int tail = inStart;
while (pre[preStart] != inorder[tail]) {
leftNum++;
tail++;
}
// 若leftNum > 1 说明当前root有左子树就设置
if (leftNum > 0) {
root.left = construction(pre, preStart + 1, preStart + leftNum,
inorder, inStart, inStart + leftNum - 1);
}
// 判断是否存在右子树
if (leftNum < preEnd - preStart) {
root.right = construction(pre, preStart + leftNum + 1, preEnd,
inorder, inStart + leftNum + 1, inEnd);
}
return root;
}
//方法二:
class Solution {
HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
//将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
//三个索引分别为
//当前根的的索引
//递归树的左边界,即数组左边界
//递归树的右边界,即数组右边界
return recur(0,0,inorder.length-1);
}
TreeNode recur(int pre_root, int in_left, int in_right){
if(in_left > in_right) return null;// 相等的话就是自己
TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点
int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量
//左子树的根的索引为先序中的根节点+1
//递归左子树的左边界为原来的中序in_left
//递归右子树的右边界为中序中的根节点索引-1
root.left = recur(pre_root+1, in_left, idx-1);
//右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
//递归右子树的左边界为中序中当前根节点+1
//递归右子树的有边界为中序中原来右子树的边界
root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);
return root;
}
}
}
6.双栈实现队列
栈判空要用isEmpty(),不要用 !=null.
/**
* title:用两个栈实现队列
* 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,
* 分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead操作返回 -1 )
*/
public class CQueue {
Stack<Integer> addStack;
Stack<Integer> deleteStack;
public CQueue() {
addStack = new Stack<>();
deleteStack = new Stack<>();
}
public void appendTail(int value) {
addStack.push(value);
}
//方法一
public int deleteHead() {
if (!deleteStack.isEmpty()) {
return deleteStack.pop();
}
if (addStack.isEmpty()) {
return -1;
}
while (!addStack.isEmpty()) {
deleteStack.add(addStack.pop());
}
return deleteStack.pop();
}
//方法二
public int deleteHead() {
if (tail.isEmpty()){
if (head.isEmpty()){
return -1;
}
while (!head.isEmpty()) {
tail.push(head.pop());
}
return tail.pop();
}else{
return tail.pop();
}
7.斐波那契数列
/**
* title:斐波那契数列
* 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
* F(0) = 0, F(1)= 1
* F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
* 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
*/
public class Fib {
//方法一
public int fib(int n) {
final int MOD = 1000000007;
if (n < 2) {
return n;
}
int p = 0, q = 0, r = 1;
for (int i = 2; i <= n; ++i) {
p = q;
q = r;
r = (p + q) % MOD;
}
return r;
}
// 方法二
public int fib(int n) {
if (n < 2) return n;
return (fib(n - 1) + fib(n - 2)) % 1000000007;
}
}
8.青蛙跳台阶问题
/**
* title:青蛙跳台阶问题
* 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
*/
public class Steps {
public int numWays(int n) {
int f0 = 1, f1 = 1;
for (int i = 2; i <= n; i++) {
f1 = f0 + f1;
f0 = f1 - f0;
f1 = f1 % 1000000007;
}
return f1;
}
}
9.旋转数组的最小数字
有序数组,尝试二分查找
/**
* title:旋转数组的最小数字
* 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
* 给你一个可能存在重复元素值的数组numbers,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。
* 请返回旋转数组的最小元素。例如,数组[3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。
*/
public class ArrayMinNum {
public int minArray(int[] numbers) {
int left = 0, right = numbers.length - 1, mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (numbers[mid] < numbers[right]) {
right = mid;
} else if (numbers[mid] > numbers[right]) {
left = mid + 1;
} else {
right--;
}
}
return numbers[right];
}
}
10.矩阵中的路径
- 对于每个格子均有可能是起始节点,并且每个路径和其他路径没有太多可利用关系,所以要进行所有的格子作为起始结点来判断。
- 由于要进行方向定位,像迷宫一样进行递归回缩。
/**
* 给定一个m x n 二维字符网格board 和一个字符串单词word 。如果word 存在于网格中,返回 true ;
* 否则,返回 false 。
* 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。
* 同一个单元格内的字母不允许被重复使用。
*/
public class MatchRoad {
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || board[0].length == 0 || word.length() == 0) {
return false;
}
int row = board.length;
int line = board[0].length;
// 用于记录是否走过该路径
boolean[][] isVisited = new boolean[row][line];
// 对于每一个元素进行起始的判断
for (int i = 0; i < row; i++) {
for (int j = 0; j < line; j++) {
if (hasStr(board, i, j, word.toCharArray(), 0, isVisited)) {
return true;
}
}
}
return false;
}
/**
* @param board 元素矩阵
* @param row 判断matrix列位置元素
* @param line 判断matrix行位置元素
* @param str 寻找的目标串
* @param index 目标串的第几个元素
* @param isVisited 记录是否走过的表格
* @return 该路径是否可行
*/
private boolean hasStr(char[][] board, int row, int line, char[] str, int index, boolean[][] isVisited) {
// 此时说明index前面的所有元素均已经匹配成功
if (str.length == index) {
return true;
}
// 不符合条件返回不通行
if (row >= board.length || line >= board[0].length || row < 0 || line < 0
|| isVisited[row][line] || board[row][line] != str[index]) {
return false;
}
index++;
// 标记该路已经走过
isVisited[row][line] = true;
// 向四个方向均进行尝试
boolean hasStrFlag = hasStr(board, row + 1, line, str, index, isVisited)
|| hasStr(board, row, line + 1, str, index, isVisited) ||
hasStr(board, row - 1, line, str, index, isVisited) ||
hasStr(board, row, line - 1, str, index, isVisited);
// 若没有成功将isVisited值进行还原避免影响后续判断,若为真那么在该步骤结束时循环,不会对后来的产生影响
if (!hasStrFlag) {
isVisited[row][line] = false;
}
return hasStrFlag;
}
}
11.机器人的运动范围
- 该题的起始范围已经给定(0,0),只需要向上下左右进行递归回溯,另外在递归时要记录路径个数。
/**
* title:机器人的运动范围
* 地上有一个M行N列的方格,一个机器人从坐标( 0, 0 )的个格子开始移动,他每次可向左右上下四个方向移动一个格子,
* 但不能进入行坐标和列坐标的各个位数之和大于K值,并且到达每个格子之前必定可以通过其他格子到达该格子。
* 例如: k=18,机器人可以进入(35,37),3+5+3+7=18<=18能进入,(36,38),3+6+3+8=20>18,不能进入。
*/
public class RobotRoad {
public int movingCount(int m, int n, int k) {
boolean[][] isVisited = new boolean[m][n];
return dfs(0, 0, m, n, k, isVisited);
}
private int dfs(int i, int j, int m, int n, int k, boolean[][] isVisited) {
if (i < 0 || j < 0 || i >= m || j >= n || (getDigits(i) + getDigits(j) > k) || isVisited[i][j]) {
return 0;
}
isVisited[i][j] = true;
return dfs(i + 1, j, m, n, k, isVisited) + dfs(i, j + 1, m, n, k, isVisited) + 1;
}
private int getDigits(int num) {
int sum = 0;
while (num != 0) {
sum += num % 10;
num /= 10;
}
return sum;
}
}
12.剪绳子
/**
* title: 剪绳子
* 题目: 给你一根长度为N的绳子,请把绳子剪成M段 , M和N都是整数且均大于1,
* 每段绳子的长度记作K1,K2 … Km,请问它们相乘的最大乘积是多少
*/
public class CutRope {
public int cuttingRope(int n) {
if (n < 2) {
return 0;
}
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
//动态规划
int[] maxValues = new int[n + 1];
maxValues[0] = 0;
maxValues[1] = 1;
maxValues[2] = 2;
maxValues[3] = 3;
for (int i = 4; i <= n; i++) {
int max = 0;
//将N看成i计算i的最大值
for (int j = 0; j <= i / 2; j++) {
max = Math.max(max, maxValues[j] * maxValues[i - j]);
}
maxValues[i] = max;
}
return maxValues[n];
}
}
13.**剪绳子2(数据更大)
/**
* title: 剪绳子2(数据更大)
* 题目: 给你一根长度为N的绳子,请把绳子剪成M段 , M和N都是整数且均大于1,
* 每段绳子的长度记作K1,K2 … Km,请问它们相乘的最大乘积是多少
*/
public class CutRopePlus {
//todo:贪心解法
public int cuttingRope(int n) {
if (n < 4) {
return n - 1;
}
long res = 1;
while (n > 4) {
res = res * 3 % 1000000007;
n -= 3;
}
return (int) (res * n % 1000000007);
}
}
14.二进制中1的个数
这是Java自带的方法,返回二进制树中的 ‘1’。
/**
* title: 二进制中1的个数
* 编写一个函数,输入是一个无符号整数(以二进制串的形式),
* 返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)。
*/
public class NumberOf1 {
public int hammingWeight(int n) {
//方法一
return Integer.bitCount(n);
//方法二
}
}
15.数值的整数次方
/**
* title:数值的整数次方
* 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。不得使用库函数,同时不需要考虑大数问题。
*/
public class NumberPow {
//方法一
public double myPow(double x, int n) {
boolean nNegative = n < 0;
double result = 1.0;
while (n != 0) {
if (1 == (n & 1)) {
result *= x;
}
x *= x;
n /= 2;
}
return nNegative ? 1.0 / result : result;
}
//方法二
public double myPow(double x, int n) {
if (n == 0) return 1;
if (n == 1) return x;
if (n == -1) return 1.0 / x;
double half = myPow(x, n / 2);
double mod = myPow(x, n % 2);
return half * half * mod;
}
}
16.打印从1到最大的n位数
/**
* title:打印从1到最大的n位数
* 输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。
* 比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
*/
public class PrintNumber {
public int[] printNumbers(int n) {
if (n <= 0) {
return null;
}
int count = (int) Math.pow(10, n) - 1;
int[] ints = new int[count];
for (int i = 0; i < count; i++) {
ints[i] = i + 1;
}
return ints;
}
}
17.删除链表的节点
/**
* title:删除链表的节点
* 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
* 返回删除后的链表的头节点。
*/
public class DeleteNode {
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode deleteNode(ListNode head, int val) {
ListNode node = head;
if (node.val == val) {
return node.next;
}
while (node.next != null) {
if (node.next.val == val) {
ListNode temp = node.next;
node.next = temp.next;
break;
}
node = node.next;
}
return head;
}
}
18.**正则表达式匹配
/**
* title:正则表达式匹配
* 请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,
* 而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,
* 匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但与"aa.a"和"ab*a"均不匹配。
*/
public class MatchStr {
public boolean isMatch(String s, String p) {
if (p.isEmpty()) {
return s.isEmpty();
}
boolean firstMatch = (!s.isEmpty() && (p.charAt(0) == s.charAt(0) || p.charAt(0) == '.'));
if (p.length() >= 2 && p.charAt(1) == '*') {
return (isMatch(s, p.substring(2)) || (firstMatch && isMatch(s.substring(1), p)));
} else {
return firstMatch && isMatch(s.substring(1), p.substring(1));
}
}
}
19.**表示数值的字符串
/**
* title:表示数值的字符串
* 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
*/
public class IsNumber {
public static boolean isNumber(String s) {
if (s == null || s.length() == 0) { //输入为空,直接返回false
return false;
}
s = s.trim(); //先对字符串裁剪一下,去掉字符串前后的空格,防止空格对判断产生干扰
//这里设置三个符号位,表示前面的判断里面有没有出现过数字,小数点和e
boolean numSeen = false; //前面出现过数字,这个在判断e的时候会用到,如果出现了e,但是后面是没有数字的,是不合法的,false
boolean dotSeen = false; //前面有没有出现过小数点,如果出现小数点的时候判断,前面已经出现过小数点和e了,则是不合法的,false
boolean eSeen = false; //前面有没有出现个e,如果出现e是判断,前面已经出现过e了,或者前面还没有出现过数字,则是不合法的,false
char[] str = s.toCharArray(); //将字符串转化为字符数组方便处理
for (int i = 0; i < str.length; i++) { //对字符数组里面的字符每个都判断一下
if (str[i] >= '0' && str[i] <= '9') { //第一种情况,最常见的,出现了数字,就把numSeen设置为true
numSeen = true;
} else if (str[i] == '.') { //第二种情况,出现了小数点,判断如果已经出现过了,或者前面已经出现过了e,就返回false
if (dotSeen == true || eSeen == true) {
return false;
}
dotSeen = true; //如果没有返回false,来到这里小数点就已经出现过了,把dotSeen设为true
} else if (str[i] == 'e' || str[i] == 'E') { //第三种情况,出现e了,如果e已经出现过了,后者前面数字还没有出现过,则false
if (eSeen == true || numSeen == false) {
return false;
}
eSeen = true; //来到这里如果没有返回false,这是e已经出现过了,将ESeen设为true
numSeen = false; //出现e之后,后面必须要有数字,不然是不合法的,false
} else if (str[i] == '+' || str[i] == '-') { //第四中情况,出现正负号了,正负号的出现,只能有两种情况
if (i != 0 && str[i - 1] != 'e' && str[i - 1] != 'E') { //要么是出现在第一个字符,要么就是出现在e之后,其他都是不合法的
return false;
}
} else { //还有其他情况,遇到不是数字,不是小数点,不是e的肯定是不合法的。
return false;
}
}
return numSeen; //最后返回numSeen标志位,本来整个过程没有返回false就应该返回true的,但是还要考虑到e之后必须要跟有数字,所以返回numSeen
}
}
20.调整数组顺序使奇数位于偶数前面
/**
* title:调整数组顺序使奇数位于偶数前面
* 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
*/
public class ExchangeNum {
public int[] exchange(int[] nums) {
if (nums == null || nums.length == 0)
return nums;
int left = 0;
int right = nums.length - 1;
while (left < right) {
while (left < right && nums[left] % 2 != 0) {
left++;
}
while (left < right && nums[right] % 2 == 0) {
right--;
}
if (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
return nums;
}
}
21.链表中倒数第k个节点
/**
* title:链表中倒数第k个节点
* 输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
* 例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
*/
public class ListNodeK {
public ListNode getKthFromEnd(ListNode head, int k) {
if (head == null) {
return null;
}
int i = 0;
ListNode frontNode, resNode;
frontNode = resNode = head;
while (resNode != null && i < k) {
resNode = resNode.next;
i++;
}
while (resNode != null) {
resNode = resNode.next;
frontNode = frontNode.next;
}
return frontNode;
}
static class ListNode {
int value;
ListNode next;
}
}
22.反转链表
/**
* title: 反转链表
* 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
*/
public class ReverseListNode {
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode resNode, oldNode, thisNode;
resNode = null;
thisNode = head;
while (thisNode != null) {
oldNode = thisNode.next;
thisNode.next = resNode;
resNode = thisNode;
thisNode = oldNode;
}
return resNode;
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
}
23.**合并两个排序的链表
/**
* title: 合并两个排序的链表
* 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
*/
public class AddTwoLists {
// 迭代
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode res = new ListNode(0); // 空节点
ListNode pre = res;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
pre.next = l1;
pre = pre.next;
l1 = l1.next;
} else {
pre.next = l2;
pre = pre.next;
l2 = l2.next;
}
}
if (l1 != null) {
pre.next = l1;
}
if (l2 != null) {
pre.next = l2;
}
return res.next;
}
//递归
public ListNode mergeTwoLists2(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val <= l2.val) {
l1.next = mergeTwoLists2(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists2(l1, l2.next);
return l2;
}
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
}
24.树的子结构
输入两棵二叉树根节点A、B,判断 B 是不是 A 的子树。
- 遍历A树,若某个节点和B头节点相等就从改节点开始判断
- 每个节点的状态情况都需要判断
- 注意判null
/**
* title:树的子结构
* 输入两棵二叉树根节点A、B,判断 B 是不是 A 的子树。
*/
public class IsSubStructure {
public boolean isSubStructure(TreeNode A, TreeNode B) {
// 递归思想 大问题分解为小问题
// A, B为空 不符题意
if (A == null || B == null) return false;
// dfs(A, B) 当前节点B是否是A的子树,若不是,则同理判断当前节点的孩子节点
return dfs(A, B) || isSubStructure(A.left, B)
|| isSubStructure(A.right, B);
}
public boolean dfs(TreeNode A, TreeNode B) {
// 比较孩子节点时, B可以为空, 例如[1]是[5,1]的子树
if (B == null) return true;
// A为空, B不为空 B一定不是A子树
if (A == null) return false;
// 若两个节点的值不同 则B不可能是A的子树 相同则比较两个节点的孩子节点是否相同
return A.val == B.val && dfs(A.left, B.left)
&& dfs(A.right, B.right);
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
25.二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
- 大问题拆分成小问题,二叉树首选递归
- 该题就是将每个节点作为根节点,将左右节点进行左右交换,注意 防止节点的覆盖。
/**
* title:二叉树的镜像
* 题目:请完成一个函数,输入一个树的根节点,返回该二叉树镜像的根节点。
*/
public class MirrorTree {
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return null;
}
//为什么要先存起来呢?因为在下一步运行时,会将header.leftNode指向改变,造成无法定位之前的Node节点
TreeNode tmpNode = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmpNode);
return root;
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
26.对称二叉树
题目:请完成一个函数,用于判断二叉树是否是对称的,给定根节点,输出boolean数据。(所有的非满二叉树均不对称)
/**
* title:对称二叉树
* 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
*/
public class IsSymmetric {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return process(root, root);
}
public boolean process(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
return t1.val == t2.val && process(t1.left, t2.right) && process(t1.right, t2.left);
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
27.**顺时针打印矩阵
顺时针打印矩阵。
- 采用宏观调度的方法,不拘泥于一个个细节问题。
- 只要获取并根据每个大循环的限制条件进行限制输出。
/**
* title:顺时针打印矩阵
* 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
*/
public class SpiralOrder {
public int[] spiralOrder(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return new int[0];
int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1, index = 0;
int[] res = new int[matrix.length * matrix[0].length];
while (index < matrix.length * matrix[0].length) {
//最上面一行
for (int i = left; i <= right && index < matrix.length * matrix[0].length; i++) {
res[index++] = matrix[top][i];
}
top++;
//最右边一行
for (int i = top; i <= bottom && index < matrix.length * matrix[0].length; i++) {
res[index++] = matrix[i][right];
}
right--;
//最下面一行
for (int i = right; i >= left && index < matrix.length * matrix[0].length; i--) {
res[index++] = matrix[bottom][i];
}
bottom--;
//最左边一行
for (int i = bottom; i >= top && index < matrix.length * matrix[0].length; i--) {
res[index++] = matrix[i][left];
}
left++;
}
return res;
}
}
28.包含min函数的栈
请完成一个栈结构,能随时获取栈中最小元素,push、pop、getMin函数的时间复杂度均为 O(1)
- push和pop函数时间复杂度时间复杂度均为O(1)
- getMin函数要想实现O(1),定说明最小值可以直接获取,实际上在最开始push的时候,我们仅能判断push进来的元素和之前栈中最小元素的大小关系,并且之前的最小值也是根据每一次push进来的元素进行判断的。那么我呢就只在push的时候判断并记录即可。
/**
* title:包含min函数的栈
* 定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,
* 调用 min、push 及 pop 的时间复杂度都是 O(1)。
*/
public class MinStack {
Stack<A> mStack;
public MinStack() {
mStack = new Stack<>();
}
public void push(int x) {
if (mStack.isEmpty()) {
mStack.push(new A(x, x));
} else {
mStack.push(new A(Math.min(x, mStack.peek().min), x));
}
}
public void pop() {
mStack.pop();
}
public int top() {
return mStack.peek().x;
}
public int min() {
return mStack.peek().min;
}
}
class A {
int min;
int x;
public A(int min, int x) {
this.min = min;
this.x = x;
}
}
2022.04.02
29.栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等
/**
* title:栈的压入、弹出序列
* 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等
*/
public class ValidateStackSequences {
public boolean validateStackSequences(int[] pushed, int[] popped) {
if (pushed == null || popped == null || pushed.length != popped.length) {
return false;
}
Stack<Integer> stack = new Stack<>();
int index = 0;
for (int i = 0; i < pushed.length; i++) {
stack.push(pushed[i]);
while (!stack.isEmpty() && stack.peek() == popped[index]) {
stack.pop();
index++;
}
}
return stack.isEmpty();
}
}
30.从上到下打印二叉树(一)
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
/**
* title:从上到下打印二叉树
* 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
*/
public class LevelOrder1 {
public int[] levelOrder(TreeNode root) {
if (root == null) {
return new int[0];
}
ArrayList<Integer> resList = new ArrayList<>();
LinkedList<TreeNode> treeNodes = new LinkedList<>();
treeNodes.add(root);
while (!treeNodes.isEmpty()) {
TreeNode node = treeNodes.poll();
resList.add(node.val);
if (node.left != null) treeNodes.add(node.left);
if (node.right != null) treeNodes.add(node.right);
}
// lis直接转int数组
// 方法一
int[] res = new int[resList.size()];
for (int i = 0; i < resList.size(); i++) {
res[i] = resList.get(i);
}
return res;
//方法二
return list.stream().mapToInt(Integer::intValue).toArray();
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
31.从上到下打印二叉树 (二)
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
/**
* title:从上到下打印二叉树 II
* 从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
*/
public class levelOrder2 {
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> resList = new ArrayList<>();
LinkedList<TreeNode> treeNodes = new LinkedList<>();
if (root == null) {
return resList;
}
treeNodes.add(root);
while (!treeNodes.isEmpty()) {
int size = treeNodes.size();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = treeNodes.poll();
list.add(node.val);
if (node.left != null) treeNodes.add(node.left);
if (node.right != null) treeNodes.add(node.right);
}
resList.add(list);
}
return resList;
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
32.从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印, 第三行再按照从左到右的顺序打印,其他行以此类推。
/**
* title: 从上到下打印二叉树 III
* 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,
* 第三行再按照从左到右的顺序打印,其他行以此类推。
*/
public List<List<Integer>> levelOrder(TreeNode root) {
ArrayList<List<Integer>> resList = new ArrayList<>();
LinkedList<TreeNode> treeNodes = new LinkedList<>();
if (root == null) {
return resList;
}
treeNodes.add(root);
while (!treeNodes.isEmpty()) {
int size = treeNodes.size();
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = treeNodes.poll();
list.add(node.val);
if (node.left != null) treeNodes.add(node.left);
if (node.right != null) treeNodes.add(node.right);
}
resList.add(list);
}
for (int i = 1; i < resList.size(); i += 2) {
Collections.reverse(resList.get(i));
}
return resList;
}
33.二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。
假设输入的数组的任意两个数字都互不相同。
/**
* title:二叉搜索树的后序遍历序列
* 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。
* 假设输入的数组的任意两个数字都互不相同。
*/
public class VerifyPostorder {
public boolean verifyPostorder(int[] postorder) {
if (postorder == null || postorder.length == 0) return true;
return process(postorder, 0, postorder.length - 1);
}
private boolean process(int[] postorder, int left, int right) {
if (left >= right) return true;
int root = postorder[right]; //后序遍历,最后一个一定是根节点
int k = 0;
while (k < right && postorder[k] < root) k++; //结束后,k位置是右子树第一个节点
for (int i = k; i < right; i++) {
if (postorder[i] < root) return false; //如果右子树中存在<根节点的值,false
}
return process(postorder, left, k - 1) && process(postorder, k, right - 1);
}
}
34.二叉树中和为某一值的路径
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。。
/**
* title:二叉树中和为某一值的路径
* 给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
* 叶子节点 是指没有子节点的节点。。
*/
public class TreePathSum {
private List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
if (root == null)
return res;
ArrayList<Integer> list = new ArrayList<>();
list.add(root.val);
process(root, target, list, root.val);
return res;
}
/**
* DFS
* @param root 根节点
* @param target 目标总和
* @param list 目前符合的list
* @param num 目前的和
*/
private void process(TreeNode root, int target, ArrayList<Integer> list, int num) {
if (num == target && root.left == null && root.right == null) {
res.add(new ArrayList<Integer>(list));
} else {
if (root.left != null) {
list.add(root.left.val);
process(root.left, target, list, num + root.left.val);
list.remove(list.size() - 1);//如果此路不符合,去除最后一个节点
}
if (root.right != null) {
list.add(root.right.val);
process(root.right, target, list, num + root.right.val);
list.remove(list.size() - 1);//如果此路不符合,去除最后一个节点
}
}
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
35.复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,
还有一个 random 指针指向链表中的任意节点或者 null。
/**
* title:复杂链表的复制
* 请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,
* 还有一个 random 指针指向链表中的任意节点或者 null。
*/
public class CopyRandomList {
public Node copyRandomList1(Node head) {
if (head == null) {
return null;
}
HashMap<Node, Node> map = new HashMap<>();
Node curNode = head;
while (curNode != null) {
map.put(curNode, new Node(curNode.val));
curNode = curNode.next;
}
curNode = head;
while (curNode != null) {
// map.get(cur) 新
map.get(curNode).next = map.get(curNode.next);
map.get(curNode).random = map.get(curNode.random);
curNode = curNode.next;
}
return map.get(head);
}
public Node copyRandomList2(Node head){
if (head == null) {
return null;
}
Node cur = head;
Node next = null;
// copy node and link to every node
//1->2
//1-> 1’->2
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.val);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
// set copy node rand
//1->1’->2->2'
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.random = cur.random != null ? cur.next.random : null;
cur = next;
}
// head head.next
Node res = head.next;
cur = head;
// split
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next != null ? next.next : null;
cur = next;
}
return res;
}
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
}
36.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。
要求不能创建任何新的节点,只能调整树中节点指针的指向。
/**
* title:二叉搜索树与双向链表
* 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。
* 要求不能创建任何新的节点,只能调整树中节点指针的指向。
*/
public class TreeToDoublyList {
Node pre, head;
public Node treeToDoublyList(Node root) {
// 边界值
if (root == null) return null;
dfs(root);
// 题目要求头尾连接
pre.right = head;
head.left = pre;
// 返回头节点
return head;
}
private void dfs(Node cur) {
// 递归结束条件
if (cur == null) return;
dfs(cur.left);
// 如果pre为空,就说明是第一个节点,头结点,然后用head保存头结点,用于之后的返回
if (pre == null) head = cur;
// 如果不为空,那就说明是中间的节点。并且pre保存的是上一个节点,
// 让上一个节点的右指针指向当前节点
else pre.right = cur;
// 再让当前节点的左指针指向父节点,也就连成了双向链表
// 保存当前节点,用于下层递归创建
cur.left = pre;
pre = cur;
dfs(cur.right);
}
}
37.序列化二叉树
* 请实现两个函数,分别用来序列化和反序列化二叉树。
* 你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,
* 你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
/**
* title:序列化二叉树
* 请实现两个函数,分别用来序列化和反序列化二叉树。
* 你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,
* 你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
*/
public class CodeTree {
//相当于中序遍历
public String serialize(TreeNode root) {
if (root == null)
return "null,";
String res = root.val + ",";
res += serialize(root.left);
res += serialize(root.right);
return res;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] arr = data.split(",");
LinkedList<String> list = new LinkedList<>();
for (int i = 0; i < arr.length; i++) {
list.offer(arr[i]); //offer:末尾插入
}
return process(list);
}
private TreeNode process(LinkedList<String> list) {
String value = list.poll();
if (value.equals("null")) {
return null;
}
TreeNode node = new TreeNode(Integer.valueOf(value));
node.left = process(list);
node.right = process(list);
return node;
}
}
38.字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
/**
* title:字符串的排列
* 输入一个字符串,打印出该字符串中字符的所有排列。
*/
public class Permutation {
ArrayList<String> result;
public String[] permutation(String s) {
result = new ArrayList<String>();
char[] chars = s.toCharArray();
Arrays.main.sort(chars);
dfs(chars, new StringBuilder());
return result.toArray(new String[0]);
}
private void dfs(char[] chars, StringBuilder stringBuilder) {
if (chars.length == stringBuilder.length()) {
result.add(stringBuilder.toString());
} else {
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c ==c '*') continue; //判断该字符是否被用过
if (i > 0 && chars[i - 1] != '*' && chars[i] == chars[i - 1]) continue; //剪枝,查看是否有重复
stringBuilder.append(c);
chars[i] = '*';
dfs(chars, stringBuilder);
//回溯,消除影响
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
chars[i] = c;
}
}
}
}
39.数组中出现次数超过一半的数字
/**
* title:数组中出现次数超过一半的数字
*/
public class MajorityElement {
public int majorityElement(int[] nums) {
int count = 0;
int res = 0;
for (int i = 0; i < nums.length; i++) {
if (count == 0) {
res = nums[i];
}
count += res == nums[i] ? 1 : -1;
}
return res;
}
}
40.数据流中的中位数
* 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
* 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
/**
* title:数据流中的中位数
* 如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
* 如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
*/
//自己写版本
public class MedianFinder {
ArrayList<Integer> list;
public MedianFinder() {
list = new ArrayList<>();
}
public void addNum(int num) {
list.add(num);
}
public double findMedian() {
Collections.main.sort(list);
if (list.size() % 2 == 0) {
return (double) (list.get(list.size() / 2) + list.get(list.size() / 2 - 1)) / 2.0;
} else {
return (double) list.get(list.size() / 2);
}
}
//网上版本
PriorityQueue<Integer> bigHeap = new PriorityQueue<Integer>((n1, n2) -> n1 - n2);
PriorityQueue<Integer> smallHeap = new PriorityQueue<Integer>((n1, n2) -> n2 - n1);
public void addNum2(int num) {
bigHeap.add(num);
smallHeap.add(bigHeap.poll());
if (bigHeap.size() + 1 < smallHeap.size()) {
bigHeap.add(smallHeap.poll());
}
}
public double findMedian2() {
if (smallHeap.size() > bigHeap.size()) {
return smallHeap.peek();
} else {
return (bigHeap.peek() + smallHeap.peek()) / 2.0;
}
}
}
41.连续子数组的最大和
- 若想获取连续累加和最大值,那么前面的累加和定为非负数,若前面一部分的累加和值为负数,定不能在当前计算的最大累加和中。
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值
/**
* title:连续子数组的最大和
* 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值
*/
public class MaxSubArray {
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int res = Integer.MIN_VALUE;
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
res = Math.max(res, sum);
sum = Math.max(sum, 0);
}
return res;
}
}
42.**整数1~N中1出现的次数
/**
* title:整数1~N中1出现的次数
* 从 1~N 所有整数中出现 1 的次数。
*/
public class CountDigitOne {
public int countDigitOne(int n) {
return process(n);
}
//下面我们都用 1234 和 2345 来举例
private int process(int n) {
// 上一级递归 n = 20、10之类的整十整百之类的情况;以及n=0的情况
if (n == 0) return 0;
// n < 10 即为个位,这样子只有一个1
if (n < 10) return 1;
String s = String.valueOf(n);
//长度:按例子来说是4位
int length = s.length();
//这个base是解题速度100%的关键,本例中的是999中1的个数:300
// 99的话就是20 ; 9的话就是1 ;9999就是4000 这里大家应该发现规律了吧。
int base = (length - 1) * (int) Math.pow(10, length - 2);
//high就是最高位的数字
int high = s.charAt(0) - '0';
//cur就是当前所数量级,即1000
int cur = (int) Math.pow(10, length - 1);
if (high == 1) {
//最高位为1,1+n-cur就是1000~1234中由千位数提供的1的个数,剩下的f函数就是求1000~1234中由234产生的1的个数
return base + 1 + n - cur + process(n - high * cur);
} else {
//这个自己思考
return base * high + cur + process(n - high * cur);
}
}
}
43.**数字序列中某一位的数字
/**
* title:数字序列中某一位的数字
* 数字以0123456789101112131415…的格式序列化到一个字符序列中。
* 在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
*/
public class FindNthDigit {
public int findNthDigit(int n) {
if (n <= 0) {
return 0;
}
//1.确定在那个数位中 1`9,10`99等
int digit = 1; //位数
long start = 1; //对应位数的起始值
long count = 9; // 对应位数值得个数
while (n > count) {
n -= count;
digit++;
start *= 10;
count = digit * start * 9;
}
//2.确定在那个数字中 15,1092等
long num = start + (n - 1) / digit;
//3.找到对应的数字
return Long.toString(num).charAt((n - 1) % digit) - '0';
}
}
44.把数组排成最小的数
/**
* title:把数组排成最小的数
* 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
*/
public class MinNumber {
public String minNumber(int[] nums) {
String[] str = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
str[i] = String.valueOf(nums[i]);
}
Arrays.main.sort(str,(s1,s2)-> ((s1+s2).compareTo(s2+s1)));
StringBuilder builder = new StringBuilder();
for (String s : str) {
builder.append(s);
}
return builder.toString();
}
public String minNumber2(int[] nums) {
List<String> list = new ArrayList<>();
for (int num : nums) {
list.add(String.valueOf(num));
}
list.sort((o1, o2) -> (o1 + o2).compareTo(o2 + o1));
return String.join("", list);
}
}
45.动态规划:---礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。
* 你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。
* 给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
/**
* title:动态规划:---礼物的最大价值
* 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。
* 你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。
* 给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
*/
public class MaxValue {
public int maxValue(int[][] grid) {
if (grid == null) {
return 0;
}
int row = grid.length, column = grid[0].length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < column; j++) {
if (i == 0 && j == 0) continue;
else if (i == 0) grid[i][j] += grid[i][j - 1];
else if (j == 0) grid[i][j] += grid[i - 1][j];
else grid[i][j] += Math.max(grid[i][j - 1], grid[i - 1][j]);
}
}
return grid[row - 1][column - 1];
}
}
46.最长不含重复字符的子字符串
/**
* title:最长不含重复字符的子字符串
* 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
*/
public class LengthOfLongestSubstring {
public int lengthOfLongestSubstring(String s) {
if (s==null || s.length()==0) return 0;
HashMap<Character,Integer> map = new HashMap<>();
int max = 0;
int index = 0;
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (map.containsKey(ch)) index = Math.max(index,map.get(ch)+1);
max = Math.max(max,i-index+1);
map.put(ch,i);
}
return max;
}
}
47.第一个只出现一次的字符
/**
* title:第一个只出现一次的字符
* 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
*/
public class FirstUniqChar {
public char firstUniqChar(String s) {
if (s.length() == 0) return ' ';
for (int i = 0; i < s.length(); i++) {
if (s.indexOf(s.charAt(i)) == s.lastIndexOf(s.charAt(i))) {
return s.charAt(i);
}
}
return ' ';
}
}
48.数组中的逆序对
/**
* title:数组中的逆序对
* 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
* 输入一个数组,求出这个数组中的逆序对的总数。
*/
public class ReversePairs {
public int reversePairs(int[] nums) {
int len = nums.length;
if (len < 2) return 0;
int[] temp = new int[len];
return merge(nums, 0, len - 1, temp);
}
private int merge(int[] nums, int left, int right, int[] temp) {
//递归终止条件
if (left >= right) return 0;
int mid = left + (right - left) / 2; //防止溢出
//left~mid
int leftNum = merge(nums, left, mid, temp);
//mid+1~right
int rightNum = merge(nums, mid + 1, right, temp);
if (nums[mid] <= nums[mid + 1]) {
return leftNum + rightNum;
}
//跨区间
int crossNum = mergeCross(nums, left, mid, right, temp);
return leftNum + rightNum + crossNum;
}
private int mergeCross(int[] nums, int left, int mid, int right, int[] temp) {
for (int i = left; i <= right; i++) {
temp[i] = nums[i];
}
int i = left;
int j = mid + 1;
int count = 0;
for (int k = left; k <= right; k++) {
if (i == mid + 1) {
nums[k] = temp[j];
j++;
} else if (j == right + 1) {
nums[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
nums[k] = temp[i];
i++;
} else {
nums[k] = temp[j];
j++;
count += mid - i + 1;
}
}
return count;
}
}
49.二叉搜索树的第k大节点
- 普通版:
/**
* title:二叉搜索树的第k大节点
* 给定一棵二叉搜索树,请找出其中第 k 大的节点的值。
*/
public class KthLargest {
//中序遍历反过来
int k1, res;
public int kthLargest(TreeNode root, int k) {
if (root == null)
return 0;
k1 = k;
dfs(root);
return res;
}
private void dfs(TreeNode root) {
//终止条件
if (root == null || k1 == 0) return;
//右
dfs(root.right);
//根
if (--k1 == 0) res = root.val;
//左
dfs(root.left);
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
- 红黑书版
class RedBlackTree{
enum Color{
RED,
BLACK;
}
static class TreeNode{
TreeNode root;
TreeNode left;
TreeNode right;
TreeNode parent;
Color color;
int val;
public TreeNode(){};
public TreeNode(int val, TreeNode left, TreeNode right, TreeNode parent, Color color){
this.val = val;
this.left = left;
this.right = right;
this.parent = parent;
this.color = color;
}
}
private TreeNode root;
public RedBlackTree(){
}
public TreeNode getRoot(){
return root;
}
public Color getColor(TreeNode p){
return (p == null) ? Color.BLACK : p.color;
}
public void insert(int val){
if(root == null){
root = new TreeNode(val, null, null, null, Color.BLACK);
}else{
TreeNode node = root;
TreeNode recordOfParent = node;
while(node != null){
recordOfParent = node;
node = (node.val > val) ? node.left : node.right;
}
TreeNode point = new TreeNode(val, null, null, null, Color.RED);
if(recordOfParent.val < val){
recordOfParent.right = point;
}else{
recordOfParent.left = point;
}
point.parent = recordOfParent;
FixBalance(point);
}
}
private void rotateLeft(TreeNode p){
TreeNode r = p.right;
p.right = r.left;
if(r.left != null){
r.left.parent = p;
}
r.parent = p.parent;
if(p.parent == null){
root = r;
}else if(p == p.parent.left){
p.parent.left = r;
}else{
p.parent.right = r;
}
r.left = p;
p.parent = r;
}
private void rotateRight(TreeNode p){
TreeNode l = p.left;
p.left = l.right;
if(l.right != null){
l.right.parent = p;
}
l.parent = p.parent;
if(p.parent == null){
root = l;
}else if(p.parent.left == p){
p.parent.left = l;
}else{
p.parent.right = l;
}
l.right = p;
p.parent = l;
}
private void FixBalance(TreeNode node){
while(getColor(node.parent) == Color.RED){
if(node.parent.parent.left == node.parent){
TreeNode uncle = node.parent.parent.right;
if(getColor(uncle) == Color.RED){
node.parent.color = Color.BLACK;
uncle.color = Color.BLACK;
node.parent.parent.color = Color.RED;
node = node.parent.parent;
}else{
if(node.parent.right == node){
node = node.parent;
rotateLeft(node);
}
node.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
rotateRight(node.parent.parent);
}
}else{
TreeNode uncle = node.parent.parent.left;
if(getColor(uncle) == Color.RED){
node.parent.color = Color.BLACK;
uncle.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
node = node.parent.parent;
}else{
if(node == node.parent.left){
node = node.parent;
rotateRight(node);
}
node.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
rotateLeft(node.parent.parent);
}
}
}
root.color = Color.BLACK;
}
}
class Solution {
int ans, Cnt = 0;
public void dfs(RedBlackTree.TreeNode root, int k){
if(root == null) return;
dfs(root.right, k);
Cnt++;
if(Cnt == k){
ans = root.val;
return;
}
dfs(root.left, k);
}
public int kthLargest(TreeNode root, int k) {
Stack<TreeNode> stack = new Stack<>();
RedBlackTree rbTree = new RedBlackTree();
while(!stack.isEmpty() || root != null){
while(root != null){
rbTree.insert(root.val);
stack.push(root);
root = root.left;
}
root = stack.pop();
root = root.right;
}
RedBlackTree.TreeNode root1 = rbTree.getRoot();
dfs(root1,k);
return ans;
}
}
50.二叉树的深度
/**
* title:二叉树的深度
* 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径
* ,最长路径的长度为树的深度。
*/
public class MaxDepth {
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return left > right ? left + 1 : right + 1;
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
51.平衡二叉树
/**
* title:平衡二叉树
* 输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,
* 那么它就是一棵平衡二叉树。
*/
public class IsBalanced {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
return Math.abs(leftDepth - rightDepth) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
public int getDepth(TreeNode root) {
if (root == null) return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
return left > right ? left + 1 : right + 1;
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
}
52.**数组中数字出现的次数
/**
* title:数组中数字出现的次数
* 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。
* 请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
*/
public class SingleNumbers {
// 假设结果数为A B
public int[] singleNumbers(int[] nums) {
int x = 0; // 用于记录 A B 的异或结果
/** 得到A^B的结果
基于异或运算的以下几个性质
1. 交换律
2. 结合律
3. 对于任何数x,都有x^x=0,x^0=x
*/
for (int val : nums) x ^= val;
// x & (-x)本身的作用是得到最低位的1,
int flag = x & (-x);
// 而我们所需要的做到的是:利用这个1来进行分组,也就是做到将A和B区分开
// 前面已经知道,x是我们需要的结果数A和B相异或的结果,也就是说,x的二进制串上的任何一个1,都能成为区分A和B的条件
// 因此我们只需要得到x上的任意一个1,就可以做到将A和B区分开来
int res = 0; // 用于记录A或B其中一者
// 分组操作
for (int val : nums) {
// 根据二进制位上的那个“1”进行分组
// 需要注意的是,分组的结果必然是相同的数在相同的组,且还有一个结果数
// 因此每组的数再与res=0一路异或下去,最终会得到那个结果数A或B
// 且由于异或运算具有自反性,因此只需得到其中一个数即可
if ((flag & val) != 0) {
res ^= val;
}
}
// 利用先前的x进行异或运算得到另一个,即利用自反性
return new int[] {res, x ^ res};
}
}
53.数组中数字出现的次数 II
/**
* title: 数组中数字出现的次数 II
* 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
*/
public class SingleNumber {
//基础写法
public int singleNumber(int[] nums) {
if (nums == null) return 0;
Arrays.main.sort(nums);
for (int i = 0; i < nums.length-1; i += 3) {
if (nums[i] != nums[i + 1]) return nums[i];
}
return nums[nums.length - 1];
}
//网上写法
//书上解法: 如果一个数字出现3次,它的二进制每一位也出现的3次。如果把所有的出现三次的数字的二进制表示的每一位都分别加起来,那么每一位都能被3整除。 我们把数组中所有的数字的二进制表示的每一位都加起来。如果某一位能被3整除,那么这一位对只出现一次的那个数的这一肯定为0。如果某一位不能被3整除,那么只出现一次的那个数字的该位置一定为1.
public int singleNumber2(int[] nums) {
int[] ints = new int[32];
for (int num : nums) {
for (int i = 0; i < 32; i++) {
ints[i] += num & 1;
num >>= 1;
}
}
int res = 0, mod = 3;
for (int i = 0; i < 32; i++) {
res <<= 1;
res += ints[31 - i] % mod;
}
return res;
}
}
54.和为s的两个数字
/**
* title:和为s的两个数字
* 输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。
* 如果有多对数字的和等于s,则输出任意一对即可。
*/
public class TwoSum {
//基础操作
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length == 0) return new int[]{};
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
set.add(nums[i]);
}
for (int i = 0; i < nums.length; i++) {
if (set.contains(target - nums[i])) return new int[]{nums[i], target - nums[i]};
}
return new int[]{};
}
//双指针
public int[] twoSum(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum < target) {
left++;
} else if (sum > target) {
right--;
} else {
return new int[]{nums[left], nums[right]};
}
}
return new int[0];
}
}
55.**和为s的连续正数序列
/**
* title:和为s的连续正数序列
* 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
* 序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
*/
public class FindContinuousSequence {
public int[][] findContinuousSequence(int target) {
//滑动窗口:朴素解法
//初始化:
ArrayList<int[]> list = new ArrayList<>();
int left = 1, right = 2;
int sum = 3;
//双指针遍历:
//整个循环中,每次移动指针时都需要更新s的值;
//如果是每次循环之前按照等差数列求和公式计算sum,则不用。
while(left<right){//至少含有两个数: 两指针没有边界限制
if(sum == target){
//符合时,循环存入
int[] temp = new int[right-left+1];
for(int i = 0; i < temp.length; i++){
temp[i] = left+i;
}
list.add(temp);
sum -= left;//记得更新sum
left++;
}else if(sum < target){
right++;
sum += right;//记得更新sum
}else{
sum -= left;//记得更新sum
left++;
}
}
//返回结果:转换并返回;也可以直接toArray(T[] a)返回
int[][] res = new int[list.size()][];
for(int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
}
56.翻转单词顺序
/**
* title:翻转单词顺序
* 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,
* 标点符号和普通字母一样处理。例如输入字符串"I am a student. ",则输出"student. a am I"。
*/
public class ReverseWords {
public String reverseWords(String s) {
s = s.trim();
int nsi = s.length() - 1, j = i;
StringBuilder res = new StringBuilder();
while (j >= 0) {
while (j >= 0 && s.charAt(j) != ' ') j--; //定位空格
res.append(s.substring(j + 1, i + 1) + " "); // 添加单词
while (j >= 0 && s.charAt(j) == ' ') j--; //跳过空格
i = j;
}
return res.toString().trim();
}
}
57.左旋转字符串
/**
* title:左旋转字符串
* 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。
* 比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
*/
public class ReverseLeftWords {
public String reverseLeftWords(String s, int n) {
return s.substring(n, s.length()) + s.substring(0, n);
}
public String reverseLeftWords(String s, int n) {
return (s.substring(n, s.length()) + s.substring(0, n)).trim();
}
}
58.**滑动窗口的最大值
/**
* title:滑动窗口的最大值
* 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
*/
public class MaxSlidingWindow {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) return new int[]{};
nuirnu
int[] res = new int[nums.length - k + 1];
LinkedList<Integer> q = new LinkedList<>();
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (q.size() > 0 && i - q.peekFirst() >= k) {
q.pollFirst();
}
while (q.size() > 0 && nums[i] > nums[q.peekLast()]) {
q.pollLast();
}
q.add(i);
if (i >= k - 1) {
res[index++] = nums[q.peekFirst()];
}
}
return res;
}
}
59.**队列的最大值
/**
* title:队列的最大值
* 请定义一个队列并实现函数 max_value 得到队列里的最大值,
* 要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
*/
class MaxQueue {
LinkedList<Integer> queue;
LinkedList<Integer> deQue; //双端队列记录最大值
public MaxQueue() {
queue = new LinkedList<Integer>() {
};
deQue = new LinkedList<>();
}
public int max_value() {
//取双端队列的队首作为最大值
return deQue.isEmpty() ? -1 : deQue.peek();
}
public void push_back(int value) {
queue.add(value);
while (!deQue.isEmpty() && deQue.peekLast() < value) {
deQue.pollLast();
}
deQue.add(value);
}
public int pop_front() {
int value = !queue.isEmpty() ? queue.poll() : -1;
if (!deQue.isEmpty() && deQue.peekFirst() == value) {
deQue.pollFirst();
}
return value;
}
}
60.**n个骰子的点数
/**
* title:n个骰子的点数
* 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
*/
public class DicesProbability {
public double[] dicesProbability(int n) {
//方案总数 6^n
double all = Math.pow(6, n);
//状态数组
int[][] dp = new int[n + 1][6 * n + 1];
dp[0][0] = 1;
//dp[i][j]有i个骰子,点数和为j的方案总数
//先统计每个点数可行的方案数量
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= 6 * i; j++) {
for (int k = 1; k <= Math.min(j, 6); k++) {
dp[i][j] += dp[i - 1][j - k];
}
}
}
double[] res = new double[1 + 5 * n];
for (int i = n; i <= 6 * n; i++) {
res[i - n] = dp[n][i] / all; //然后再换成概率
}
return res;
}
// 动态规划,时间复杂度:O(n * n) 空间复杂度:O(n * n)
public double[] dicesProbability(int n) {
// dp[i][j]表示投掷i颗骰子时,在所有出现的结果中,出现点数为j的次数
int[][] dp = new int[n + 1][6 * n + 1];
// 初始化:投掷一颗骰子时,1到6每个点数出现一次
for (int j = 1; j <= 6; ++j) {
dp[1][j] = 1;
}
// 投掷i颗骰子
for (int i = 2; i <= n; ++i) {
// 当前这颗骰子的点数
for (int k = 1; k <= 6; ++k) {
// 计算点数为j的出现次数
for (int j = 0; j <= 6 * i; ++j) {
// 如果j-k这个点数在投掷前i-1颗骰子时出现过,那么它再加上现在的点数k后,点数j就存在
if (j - k > 0 && dp[i - 1][j - k] > 0) {
// 点数j的出现次数=当前骰子是其他点数时已经出现过的次数+当前骰子是k时点数j的出现次数
dp[i][j] += dp[i - 1][j - k];
}
}
}
}
// 统计出现了多少种可能,以及每种可能的出现次数
List<Integer> list = new ArrayList<>();
double total = 0;
for (int i = n * 1; i <= n * 6; ++i) {
if (dp[n][i] != 0) {
list.add(dp[n][i]);
total += dp[n][i];
}
}
// 计算每种可能的概率
int size = list.size();
double[] res = new double[size];
for (int i = 0; i < size; ++i) {
res[i] = list.get(i) / total;
}
return res;
}
}
61.扑克牌中的顺子
/**
* title:扑克牌中的顺子
* 从若干副扑克牌中随机抽 5 张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,
* A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
*/
public class IsStraight {
public boolean isStraight(int[] nums) {
if (nums.length != 5) return false;
Arrays.main.sort(nums);
//去除0
int k = 0;
while (nums[k] == 0) {
k++;
}
//判断是否有重复数字
for (int i = k + 1; i < nums.length; i++) {
if (nums[i] == nums[i - 1]) return false;
}
return nums[4] - nums[k] <= 4;
}
}
62.股票的最大利润
/**
* title:股票的最大利润
* 假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
*/
public class MaxProfit {
public int maxProfit(int[] prices) {
if (prices.length == 0 || prices == null) return 0;
int maxValue = Integer.MIN_VALUE;
int minCost = prices[0];
for (int i = 0; i < prices.length; i++) {
maxValue = Math.max(prices[i] - minCost, maxValue);
minCost = Math.min(minCost, prices[i]);
}
return maxValue;
}
}
63.求1+2+…+n
/**
* title:求1+2+…+n
* 求 1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
*/
public class SumNums {
//递归代替for,try..catch代替if
int[] res = new int[]{0};
public int sumNums(int n) {
try {
return res[n];
} catch (Exception e) {
return n + sumNums(n - 1);
}
}
public int sumNums(int n) {
boolean flag = n == 1 || (n += (sumNums(n-1))) > 0;
return n;
}
}
64.两个链表的相同节点
/**
* title:两个链表的相同节点
*/
public class GetIntersectionNode {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode mA = headA;
ListNode mB = headB;
while (mA != mB) {
if (mA == null) {
mA = headB;
} else {
mA = mA.next;
}
if (mB == null) {
mB = headA;
} else {
mB = mB.next;
}
}
return mA;
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
}
65.0~n-1中缺失的数字
/**
* title:0~n-1中缺失的数字
* 一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。
* 在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
*/
public class MissingNumber {
public int missingNumber(int[] nums) {
// 计算出 n*(n+1)/2 ,减去nums取和,就是答案
int sum = nums.length * (nums.length + 1) / 2;
return sum- Arrays.stream(nums).sum();
}
public int missingNumber(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] != mid) {
right = mid;
} else {
left = mid + 1;
}
}
if (nums[right] == right) {
right++;
}
return right;
}
}
66.丑数
/**
* title:丑数
* 我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
*/
public class NthUglyNumber {
int nthUglyNumber(int n) {
if (n <= 0) {
return 0;
}
int p2 = 0, p3 = 0, p5 = 0;
int[] res = new int[n];
res[0] = 1;
for (int i = 1; i < n; i++) {
int num1 = 2 * res[p2];
int num2 = 3 * res[p3];
int num3 = 5 * res[p5];
res[i] = Math.min(num1, Math.min(num2, num3));
if (res[i] == num1) p2++;
if (res[i] == num2) p3++;
if (res[i] == num3) p5++;
}
return res[n - 1];
}
}
67.在排序数组中查找数字 I
/**
* title:在排序数组中查找数字 I
* 统计一个数字在排序数组中出现的次数。
*/
public class SearchNum {
//遍历O(n), 二分查找O(logn)
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
int start = binarySearch(nums, target), end = binarySearch(nums, target + 1);
return end - start + (nums[end] == target ? 1 : 0);
}
int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
68.把数字翻译成字符串
/**
* title:把数字翻译成字符串
* 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1
* 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。
* 请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
*/
public class TranslateNum {
public int translateNum(int num) {
String s = String.valueOf(num);
int a = 1, b = 1;
for (int i = 2; i <= s.length(); i++) {
String substring = s.substring(i - 2, i);
int c = substring.compareTo("10") >= 0 && substring.compareTo("25") <= 0 ? a + b : a;
b = a;
a = c;
}
return a;
}
}
69.圆圈中最后剩下的数字
/**
* title:圆圈中最后剩下的数字
* 0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。
* 求出这个圆圈里剩下的最后一个数字。
*/
public class LastRemaining {
public int lastRemaining(int n, int m) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(i);
}
int index = 0;
while (n != 1) {
index = (index + m - 1) % n;
list.remove(index);
n--;
}
return list.get(0);
}
//递归
/**
*
我们要使用的数学方法,就是从结果0号位置,反推最开始在哪
你从第二次,向上看第一次
你会发现,原来3在0的位置
现在,3在(0 + 3) % 5
=> +3 回到上次的位置
=> %5 防止数组溢出,并且数组本来就是循环数组
f(n) = ( f(n - 1) + m ) % n
解释意思:
f(n) 表示上一次
f(n - 1) 表示这次,因为我们要从这次回推上一次
m 表示隔几个
n表示上一次的数组长度
*/
public int lastRemaining(int n, int m) {
int ans = 0;
// 最后一轮剩下2个人,所以从2开始反推
for (int i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
}
public int lastRemaining1(int n, int m) {
if (n == 1) return 0;
return (lastRemaining1(n - 1, m) + m) % n;
}
}
70.最小的k个数
/**
* title:最小的k个数
* 输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,
* 则最小的4个数字是1、2、3、4。
*/
public class GetLeastNumbers {
public static void main(String[] args) {
int[] arr = {1, 2, 6, 23, 0, 65, -4};
System.out.println(Arrays.toString(getLeastNumbers(arr, 3)));
}
public static int[] getLeastNumbers(int[] arr, int k) {
if (arr == null || k < 0) {
return null;
}
Arrays.main.sort(arr);
return Arrays.copyOfRange(arr, 0, k);
}
}
71、不用加减乘除做加法
/**
* title: 不用加减乘除做加法
* 写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
*/
public class O68 {
public int add(int a, int b) {
while (b != 0) { //当进位是0,跳出
int c = (a & b) << 1; // c = 进位
a ^= b; // a = 非进位和
b = c; // b= 进位
}
return a;
}
}
72、构建乘积数组
/**
* title: 构建乘积数组
* 给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积,
* 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。
*/
public class O69 {
public int[] constructArr(int[] a) {
int n = a.length;
int[] B = new int[n];
for (int i = 0, product = 1; i < n; product *= a[i], i++) /* 从左往右累乘 */
B[i] = product;
for (int i = n - 1, product = 1; i >= 0; product *= a[i], i--) /* 从右往左累乘 */
B[i] *= product;
return B; // 刚好把i位置错过
}
}
73、把字符串转换成整数
/**
* title: 把字符串转换成整数
* 写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
*/
public class O70 {
public int strToInt1(String str) {
if (str == null) return 0;
str = str.trim();
int res = 0, len = str.length();
if (str.charAt(0) == '-') {
for (int i = 1; i < len; i++) {
if (str.charAt(i) < '0' || str.charAt(i) > '9') break;
res = res * 10 + str.charAt(i) - '0';
}
res = -res;
} else {
for (int i = 0; i < len; i++) {
if (str.charAt(i) < '0' || str.charAt(i) > '9') break;
res = res * 10 + str.charAt(i) - '0';
}
}
return res;
}
public int strToInt(String str) {
str = str.trim();
if (str.length() == 0) return 0;
// 跳过 "+" "-" 并 判断符号
int sign = 1;
if (str.startsWith("+") && str.length() != 1) {
sign = 1;
str = str.substring(1);
} else if (str.startsWith("-") && str.length() != 1) {
sign = -1;
str = str.substring(1);
}
// 判断第一个字符非法情况
if (str.charAt(0) < '0' || str.charAt(0) > '9')
return 0;
// 转成数字
Long res = 0l;
for (char c : str.toCharArray()) {
// 中间遇到非法字符, 直接跳出
if (c < '0' || c > '9') break;
res = res * 10 + (c - '0');
// 剪枝, 超过范围, 提前返回
if (res > Integer.MAX_VALUE)
return sign > 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
// long -> int
return Integer.parseInt(String.valueOf(res * sign));
}
}
74、二叉搜索树的最近公共祖先-1
/**
* title: 二叉搜索树的最近公共祖先-1
* 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
*/
public class O71 {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == null || q == null) return null;
while (root != null) {
if (p.val> root.val&&q.val> root.val){
root = root.right;
}else if (p.val< root.val&&q.val< root.val){
root = root.left;
}else break;
}
return root;
}
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
75、二叉树的最近公共祖先-2
/**
* title: 二叉树的最近公共祖先-2
* 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
*/
public class O72 {
/**
* 二叉树的最近公共祖先
* 思路:
* 三种情况:
* 1、p q 一个在左子树 一个在右子树 那么当前节点即是最近公共祖先
* 2、p q 都在左子树
* 3、p q 都在右子树
*/
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root == p || root == q) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
// p q 一个在左,一个在右
return root;
}
if (left != null) {
// p q 都在左子树
return left;
}
if (right != null) {
// p q 都在右子树
return right;
}
return null;
}
}