LeetCode
双指针
167.两数之和
public class leetCode167 {
public int[] twoSum(int[] numbers, int target) {
int left=0;
int right=numbers.length-1;
int res[]=new int[2];
while (left <= right) {
int sum=numbers[left]+numbers[right];
if (sum==target) {
res[0]=left+1;
res[1]=right+1;
return res;
}else if (sum<target){
left++;
}else {
right--;
}
}
return res;
}
}
88.归并两个有序数组
public class leetCode88 {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int length=m+n-1;
int len1=m-1;
int len2=n-1;
while ( len1>=0 && len2>=0) {
if (nums2[len2]>=nums1[len1]) {
nums1[length]=nums2[len2];
length--;
len2--;
}else {
nums1[length]=nums1[len1];
length--;
len1--;
}
}
while (len1>=0 ) {
nums1[length]=nums1[len1];
length--;
len1--;
}
while (len2>=0) {
nums1[length]=nums2[len2];
length--;
len2--;
}
}
}
142.环形链表二
public class LeetCode142 {
public ListNode detectCycle(ListNode head) {
ListNode fast = head, slow = head;
//找环
while (fast != null && fast.next != null) {
if (fast == null || fast.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
//找相遇点
fast = head;
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
}
动态规划
动态规划基础问题
一维
70.爬楼梯
public class leetCode70 {
public int climbStairs(int n) {
if (n<=2) return n;
int[] dp = new int[n + 1];
dp[1]=1;
dp[2]=2;
for (int i = 3; i <=n ; i++) {
dp[i]=1;
dp[i]=Math.max(dp[i],dp[i-1]+dp[i-2]);
}
return dp[n];
}
}
413.等差数列划分
public class leetCode413 {
public int numberOfArithmeticSlices(int[] nums) {
int n=nums.length;
if (n<3) return 0;
int[] dp = new int[n];
for (int i = 2; i < n; i++) {
if (nums[i]-nums[i-1]==nums[i-1]-nums[i-2])
dp[i]=dp[i-1]+1;
}
int cnt=0;
for (int i : dp) {
cnt+=i;
}
return cnt;
}
}
二维
64.最小路径和
public class leetCode64 {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) dp[i][j] = grid[0][0];
else if (i==0) dp[i][j]=dp[i][j-1]+grid[i][j];
else if (j==0) dp[i][j]=dp[i-1][j]+grid[i][j];
else dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m-1][n-1];
}
}
542.01矩阵
//https://leetcode-cn.com/problems/01-matrix/solution/2chong-bfs-xiang-jie-dp-bi-xu-miao-dong-by-sweetie/
public class leetCode542 {
public int[][] updateMatrix(int[][] mat) {
int m = mat.length;
int n = mat[0].length;
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
dp[i][j] = mat[i][j] == 0 ? 0 : 10000;
}
}
//从左上角开始
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i-1>=0) dp[i][j]=Math.min(dp[i][j],dp[i-1][j]+1);
if (j-1>=0) dp[i][j]=Math.min(dp[i][j],dp[i][j-1]+1);
}
}
//从右下角开始
for (int i = m-1; i >= 0; i--) {
for (int j = n-1; j >= 0; j--) {
if (i+1<m) dp[i][j]=Math.min(dp[i][j],dp[i+1][j]+1);
if (j+1<n) dp[i][j]=Math.min(dp[i][j],dp[i][j+1]+1);
}
}
return dp;
}
}
221.最大正方形
package 动态规划.动划基础问题.二维;
//思想:将每个格子看成正方形的右下角,看其余三个角的最小边
//状态转移方程:dp[i][j] = min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
//
//作者:lingyunxin
//链接:https://leetcode-cn.com/problems/maximal-square/solution/dong-tai-gui-hua-jian-dan-python-by-ling-a064/
//来源:力扣(LeetCode)
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
public class leetCode221 {
public int maximalSquare(char[][] matrix) {
int m=matrix.length;
int n=matrix[0].length;
int[][] dp = new int[m][n];
int maxLen=0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i==0||j==0) dp[i][j]=matrix[i][j]-'0';
else if (matrix[i][j]=='1')dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
maxLen=Math.max(maxLen,dp[i][j]);
}
}
return maxLen*maxLen;
}
}
背包问题
01背包
416.分割等和子集
动规五部曲分析如下:
1.确定dp数组以及下标的含义
01背包中,dp[i] 表示: 容量为j的背包,所背的物品价值可以最大为dp[j]。
套到本题,dp[i]表示 背包总容量是i,最大可以凑成i的子集总和为dp[i]。
2.确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.dp数组如何初始化
在01背包,一维dp如何初始化,已经讲过,
从dp[j]的定义来看,首先dp[0]一定是0。
如果如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了。
本题题目中 只包含正整数的非空数组,所以非0下标的元素初始化为0就可以了。
代码如下:
// 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
// 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
vector<int> dp(10001, 0);
4.确定遍历顺序
在动态规划:关于01背包问题,你该了解这些!(滚动数组) (opens new window)中就已经说明:如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒叙遍历!
代码如下:
// 开始 01背包
for(int i = 0; i < nums.size(); i++) {
for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
5.举例推导dp数组
dp[i]的数值一定是小于等于i的。
如果dp[i] == i 说明,集合中的子集总和正好可以凑成总和i,理解这一点很重要。
用例1,输入[1,5,11,5] 为例,如图:
public class leetCode416 {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum % 2 != 0) return false;
int target = sum / 2;
int m = nums.length;
boolean[][] dp = new boolean[m + 1][target + 1];
for (int i = 0; i <= m; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= target; j++) {
//如果背包容量不足
if (j < nums[i - 1]) {
dp[i][j] = dp[i - 1][j];
} else {//装或者不装该物品
dp[i][j] = dp[i - 1][j - nums[i - 1]] | dp[i - 1][j];//使用 | 避免短路
}
}
}
return dp[m][target];
}
//压缩空间
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) return false;
int n = nums.length;
int sum = 0;
for(int num : nums){
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) return false;
int target = sum / 2;
int[] dp = new int[target + 1];
for(int i = 0; i < n; i++){
for(int j = target; j >= nums[i]; j--){
//物品 i 的重量是 nums[i],其价值也是 nums[i]
dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
}
}
return dp[target] == target;
}
}
474.一和零
public class leetCode474 {
public int findMaxForm(String[] strs, int m, int n) {
int[][] dp = new int[m + 1][n + 1];
//遍历物品
for (String str : strs) {
char[] chars=str.toCharArray();
int oneNum=0,zeroNum=0;
for (char c : chars) {
if (c=='0') zeroNum++;
else oneNum++;
}
// 遍历背包容量且从后向前遍历
for (int i = m; i >=zeroNum ; i--) {
for (int j = n; j >=oneNum ; j--) {
//装或者不装
dp[i][j]=Math.max(dp[i][j],dp[i-zeroNum][j-oneNum]+1);
}
}
}
return dp[m][n];
}
}
494.目标和
public class leetCode494 {
public int findTargetSumWays(int[] nums, int target) {
int sum=0;
for (int num : nums) {
sum+=num;
}
if ((target+sum)%2==1) return 0;
int size=(target+sum)/2;
if (size<0) size=-size;
int[] dp = new int[size + 1];
dp[0]=1;
for (int i = 0; i < nums.length; i++) {
for (int j = size; j >=nums[i] ; j--) {
dp[j]+=dp[j-nums[i]];
}
}
return dp[size];
}
}
完全背包
70.爬楼梯
动规五部曲分析如下:
确定dp数组以及下标的含义
dp[i]:爬到有i个台阶的楼顶,有dp[i]种方法。
确定递推公式
在动态规划:494.目标和 (opens new window)、 动态规划:518.零钱兑换II (opens new window)、动态规划:377. 组合总和 Ⅳ (opens new window)中我们都讲过了,求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];
本题呢,dp[i]有几种来源,dp[i - 1],dp[i - 2],dp[i - 3] 等等,即:dp[i - j]
那么递推公式为:dp[i] += dp[i - j]
dp数组如何初始化
既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0了。
下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果
确定遍历顺序
这是背包里求排列问题,即:1、2 步 和 2、1 步都是上三个台阶,但是这两种方法不一样!
所以需将target放在外循环,将nums放在内循环。
每一步可以走多次,这是完全背包,内循环需要从前向后遍历。
举例来推导dp数组
介于本题和动态规划:377. 组合总和 Ⅳ (opens new window)几乎是一样的,这里我就不再重复举例了。
代码如下:
class Solution {
public int climbStairs(int n) {
int[] dp = new int[n + 1];
int[] weight = {1,2};
dp[0] = 1;
for (int i = 0; i <= n; i++) {
for (int j = 0; j < weight.length; j++) {
if (i >= weight[j]) dp[i] += dp[i - weight[j]];
}
}
return dp[n];
}
}
public class leetCode70 {
public int climbStairs(int n) {
if (n<=2) return n;
int[] dp = new int[n + 1];
dp[1]=1;
dp[2]=2;
for (int i = 3; i <= n; i++) {
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
public int climbStairs1(int n) {
if (n<=2) return n;
int cur=0,a=1,b=2;
for (int i = 3; i <= n; i++) {
cur=a+b;
a=b;
b=cur;
}
return cur;
}
}
279.完全平方数
public class leetCode279 {
public int numSquares(int n) {
int[] dp = new int[n + 1];
dp[0]=0;
for (int i = 1; i <= n; i++) {
dp[i]=i;
for (int j = 1; j *j<=i ; j++) {
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
}
322.零钱兑换
public int coinChange(int[] coins, int amount) {
//定义数组,表示金额从0到amount对应的最少硬币个数
int[] dp = new int[amount + 1];
//将数组元素填充为大于最大硬币个数的值
Arrays.fill(dp, amount + 1);
dp[0] = 0;
//控制总金额
for (int i = 1; i <= amount; i++) {
//控制硬币面额
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
//找出总金额减去当前硬币面额后对应的最少硬币个数
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
139.拆分单词
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
boolean[] valid = new boolean[s.length() + 1];
valid[0] = true;
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j < i; j++) {
if (wordDict.contains(s.substring(j,i)) && valid[j]) {
valid[i] = true;
}
}
}
return valid[s.length()];
}
}
子序列问题
子序列(不连续)
300.最长递增子序列
1.dp[i]的定义
dp[i]表示i之前包括i的最长上升子序列的长度。
2.状态转移方程
位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。
3.dp[i]的初始化
每一个i,对应的dp[i](即最长上升子序列)起始大小至少都是是1.
4.确定遍历顺序
dp[i] 是有0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。
j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
5.举例推导dp数组
输入:[0,1,0,3,2],dp数组的变化如下:
public class leetCode300 {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if (n==1) return 1;
int[] dp = new int[n];
Arrays.fill(dp,1);
int maxLen=0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <i ; j++) {
if (nums[i]>nums[j]) dp[i]=Math.max(dp[i],dp[j]+1);
maxLen=Math.max(maxLen,dp[i]);
}
}
return maxLen;
}
}
376.摆动序列
public class leetCode376 {
public int wiggleMaxLength(int[] nums) {
int length = nums.length;
if (length==1) return 1;
int[] dp = new int[length];
int[] diff = new int[length];
for (int i = 1; i <length ; i++) {
diff[i]=nums[i]-nums[i-1];
}
int maxlen=0;
for (int i = 1; i <length ; i++) {
//差值如果为0,则两数相同,最短可能为1,否则至少为2
if (diff[i]==0) dp[i]=1;
else {
dp[i]=2;
maxlen=Math.max(maxlen,dp[i]);
}
for (int j = 0; j <i ; j++) {
if (diff[i]*diff[j]<0)
dp[i]=Math.max(dp[i],dp[j]+1);
maxlen=Math.max(maxlen,dp[i]);
}
}
return maxlen;
}
}
646.最长数对链
import java.util.Arrays;
public class leetCode646 {
public int findLongestChain(int[][] pairs) {
int m = pairs.length;
if (m==1) return 1;
int[] dp = new int[m];//dp[i]表示以pairs[i]这个数对结尾的最长链的长度
Arrays.sort(pairs, (o1,o2)->(o1[0]-o2[0]));
dp[0]=0;
int maxNum=0;
Arrays.fill(dp,1);
for (int i = 1; i < m; i++) {
for (int j = 0; j <i ; j++) {
if (pairs[i][0]>pairs[j][1]) dp[i]=Math.max(dp[i],dp[j]+1);
maxNum=Math.max(dp[i],maxNum);
}
}
return maxNum;
}
}
1143.最长公共子序列
1.确定dp数组(dp table)以及下标的含义
dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
有同学会问:为什么要定义长度为[0, i - 1]的字符串text1,定义为长度为[0, i]的字符串text1不香么?
这样定义是为了后面代码实现方便,如果非要定义为为长度为[0, i]的字符串text1也可以,大家可以试一试!
2.确定递推公式
主要就是两大情况: text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同
如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1;
如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。
即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
代码如下:
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
3.dp数组如何初始化
先看看dp[i][0]应该是多少呢?
test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0;
同理dp[0][j]也是0。
其他下标都是随着递推公式逐步覆盖,初始为多少都可以,那么就统一初始为0。
代码:
vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
4.确定遍历顺序
从递推公式,可以看出,有三个方向可以推出dp[i][j],如图:
那么为了在递推的过程中,这三个方向都是经过计算的数值,所以要从前向后,从上到下来遍历这个矩阵。
5.举例推导dp数组
以输入:text1 = "abcde", text2 = "ace" 为例,dp状态如图:
public class leetCode1143 {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <=n ; j++) {
if (text1.charAt(i-1)==text2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[m][n];
}
}
子序列(连续)
53.最大子序和
动规五部曲如下:
1.确定dp数组(dp table)以及下标的含义
dp[i]:包括下标i之前的最大连续子序列和为dp[i]。
2.确定递推公式
dp[i]只有两个方向可以推出来:
dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
nums[i],即:从头开始计算当前连续子序列和
一定是取最大的,所以dp[i] = max(dp[i - 1] + nums[i], nums[i]);
3.dp数组如何初始化
从递推公式可以看出来dp[i]是依赖于dp[i - 1]的状态,dp[0]就是递推公式的基础。
dp[0]应该是多少呢?
更具dp[i]的定义,很明显dp[0]因为为nums[0]即dp[0] = nums[0]。
4.确定遍历顺序
递推公式中dp[i]依赖于dp[i - 1]的状态,需要从前向后遍历。
5.举例推导dp数组
以示例一为例,输入:nums = [-2,1,-3,4,-1,2,1,-5,4],对应的dp状态如下:
public class leetCode53 {
public int maxSubArray(int[] nums) {
int n = nums.length;
if (n==1) return nums[0];
int[] dp = new int[n];
dp[0]=nums[0];
for (int i = 1; i < n; i++) {
dp[i]=dp[i-1]>0?dp[i-1]+nums[i]:nums[i];
}
int maxSum=dp[0];
for (int i = 0; i < n; i++) {
maxSum=Math.max(maxSum,dp[i]);
}
return maxSum;
}
}
编辑距离
583.两个字符串的删除操作
思路
本题和动态规划:115.不同的子序列相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。
这次是两个字符串可以相互删了,这种题目也知道用动态规划的思路来解,动规五部曲,分析如下:
1.确定dp数组(dp table)以及下标的含义
dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
这里dp数组的定义有点点绕,大家要撸清思路。
2.确定递推公式
当word1[i - 1] 与 word2[j - 1]相同的时候
当word1[i - 1] 与 word2[j - 1]不相同的时候
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
3.dp数组如何初始化
从递推公式中,可以看出来,dp[i][0] 和 dp[0][j]是一定要初始化的。
dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。
dp[0][j]的话同理,所以代码如下:
vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
4.确定遍历顺序
从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); 和dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根据左上方、正上方、正左方推出来的。
所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。
5.举例推导dp数组
以word1:"sea",word2:"eat"为例,推导dp数组状态图如下:
public class leetCode583 {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= n; j++) {
dp[0][j]=j;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <=n ; j++) {
if (word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else {
dp[i][j]=Math.min(dp[i-1][j-1]+2,Math.min(dp[i-1][j]+1,dp[i][j-1]+1));
}
}
}
return dp[m][n];
}
}
72.编辑距离
public class leetCode72 {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m + 1][n + 1];
//第一行
for (int j = 1; j <= n; j++) dp[0][j] = dp[0][j - 1] + 1;
//第一列
for (int i = 1; i <= m; i++) dp[i][0] = dp[i - 1][0] + 1;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <=n ; j++) {
if (word1.charAt(i-1)==word2.charAt(j-1)) dp[i][j]=dp[i-1][j-1];
else dp[i][j]=Math.min(dp[i-1][j-1],Math.min(dp[i-1][j],dp[i][j-1]))+1;
}
}
return dp[m][n];
}
}
回文
股票问题
121.买卖股票(只买卖1次)
动规五部曲分析如下:
1.确定dp数组(dp table)以及下标的含义
dp[i][0] 表示第i天持有股票所得最多现金 ,这里可能有同学疑惑,本题中只能买卖一次,持有股票之后哪还有现金呢?
其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。
dp[i][1] 表示第i天不持有股票所得最多现金
注意这里说的是“持有”,“持有”不代表就是当天“买入”!也有可能是昨天就买入了,今天保持持有的状态
很多同学把“持有”和“买入”没分区分清楚。
在下面递推公式分析中,我会进一步讲解。
2.确定递推公式
如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来
第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);
如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来
第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
这样递归公式我们就分析完了
3.dp数组如何初始化
由递推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]); 和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);可以看出
其基础都是要从dp[0][0]和dp[0][1]推导出来。
那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];
dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
4.确定遍历顺序
从递推公式可以看出dp[i]都是有dp[i - 1]推导出来的,那么一定是从前向后遍历。
5.举例推导dp数组
以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:
121.买卖股票的最佳时机
public int maxProfit2(int[] prices) {
int n = prices.length;
if (n<2) return 0;
int[][] dp = new int[n][2];
dp[0][0]=0;//不持有股票
dp[0][1]=-prices[0];//持有股票
for (int i = 1; i < n; i++) {
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
}
return dp[n-1][0];
}
123.买卖股票的最佳时期(2次买卖)
/*
* 定义 5 种状态:
* 0: 没有操作, 1: 第一次买入, 2: 第一次卖出, 3: 第二次买入, 4: 第二次卖出
*/
public class leetCode123 {
public int maxProfit(int[] prices) {
int n=prices.length;
if (n<2) return 0;
int[][] dp = new int[n][5];
dp[0][1]=-prices[0];
dp[0][3]=-prices[0];
for (int i = 1; i < n; i++) {
dp[i][1]=Math.max(dp[i-1][1],-prices[i]);
dp[i][2]=Math.max(dp[i-1][2],dp[i][1]+prices[i]);
dp[i][3]=Math.max(dp[i-1][3],dp[i][2]-prices[i]);
dp[i][4]=Math.max(dp[i-1][4],dp[i][3]+prices[i]);
}
return dp[n-1][4];
}
}
188.买卖股票的最佳时期(k次买卖)
package 动态规划.买卖股票问题;
// // [天数][交易次数][是否持有股票]
public class leetCode188 {
public int maxProfit(int k, int[] prices) {
int length = prices.length;
if (length<2) return 0;
int[][][] dp = new int[length][k+1][2];
// dp数组初始化
// 初始化所有的交易次数是为确保 最后结果是最多 k 次买卖的最大利润
for (int i = 0; i <=k ; i++) {
dp[0][i][1]=-prices[0];
}
for (int i = 1; i <length ; i++) {
for (int j = 1; j <= k; j++) {
//卖
dp[i][j][0]=Math.max(dp[i-1][j][0],dp[i-1][j][1]+prices[i]);
//买
dp[i][j][1]=Math.max(dp[i-1][j][1],dp[i-1][j-1][0]-prices[i]);
}
}
return dp[length-1][k][0];
}
}
714.买卖股票的最佳时期含手续费(多次买卖)
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
if (n==1||n==0) return 0;
int[][] dp = new int[2][n + 1];
dp[0][0]=0;
dp[1][0]=-prices[0];
for (int i = 1; i <n; i++) {
dp[0][i]=Math.max(dp[0][i-1],dp[1][i-1]+prices[i]-fee);
dp[1][i]=Math.max(dp[1][i-1],dp[0][i-1]-prices[i]);
}
return dp[0][n-1];
}
}
309.买卖股票的最佳时期(含冷冻期)
package 动态规划.买卖股票问题;
//https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/fei-zhuang-tai-ji-de-dpjiang-jie-chao-ji-tong-su-y/
// dp[0][0]=0;//不持有股票,没卖出的
// dp[0][1]=0;//不持有股票,卖出去了
// dp[0][2]=-1*prices[0];//持有股票,今天买入;
// dp[0][3]=-1*prices[0];//持有股票,非今天买入的;
// for(int i=1;i<prices.size();i++){
// dp[i][0]=max(dp[i-1][0],dp[i-1][1]);//前一天不持有股票的两种情况的最大值
// dp[i][1]=max(dp[i-1][2],dp[i-1][3])+prices[i];//今天卖出股票,来着前一天持有股票的最大值+pr
// dp[i][2]=dp[i-1][0]-prices[i];//今天买入股票,这前一天一定没有卖出股票
// dp[i][3]=max(dp[i-1][2],dp[i-1][3]);//今天没买股票,却持有股票,前一天继承来的,有两种情况
// }
// return max(dp[prices.size()-1][0],dp[prices.size()-1][1]);
public class leetCode309 {
public int maxProfit(int[] prices) {
//1.确定dp数组以及下标的含义 0:不持有股票,没卖出的;1:不持有股票,卖出去了;
//2.确定递推公式
//3.dp数组如何初始化
//4.确定遍历顺序
int n = prices.length;
if (n<2) return 0;
int[][] dp = new int[n][4];
dp[0][0]=0;
dp[0][1]=0;
dp[0][2]=-prices[0];
dp[0][3]=-prices[0];
for (int i = 1; i < n; i++) {
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
dp[i][1]=Math.max(dp[i-1][2],dp[i-1][3])+prices[i];
dp[i][2]=dp[i-1][0]-prices[i];
dp[i][3]=Math.max(dp[i-1][2],dp[i-1][3]);
}
return Math.max(dp[n-1][0],dp[n-1][1]);
}
}
打家劫舍问题
198.打家劫舍
public class leetCode198 {
public int rob(int[] nums) {
if (nums.length==0) return 0;
if(nums.length==1) return nums[0];
int n = nums.length;
int[] dp = new int[n];
dp[0]=nums[0];
dp[1]=Math.max(nums[0],nums[1]);
for (int i = 2; i < n; i++) {
dp[i]=Math.max(dp[i-1],nums[i]+dp[i-2]);
}
return dp[n-1];
}
public int rob1(int[] nums) {
if (nums.length==0) return 0;
int n = nums.length;
if (n==1) return nums[0];
int cur=nums[0],pre1=0,pre2=0;
for (int i = 1; i <n ; i++) {
cur=Math.max(pre2,pre1+nums[i]);
pre1=pre2;
pre2=cur;
}
return cur;
}
}
213.打家劫舍(环形)
public class leetCode213 {
public int rob(int[] nums) {
int n = nums.length;
if (n==0) return 0;
if (n==1) return nums[0];
int max1=rob(nums,0,n-2);
int max2=rob(nums,1,n-1);
return Math.max(max1,max2);
}
public int rob(int[] nums,int start,int end) {
int n = nums.length;
if (n==0) return 0;
if (n==1) return nums[0];
if (start == end) return nums[start];
int[] dp = new int[nums.length];
dp[start]=nums[start];
dp[start+1]=Math.max(nums[start],nums[start+1]);
for (int i = start+2; i <= end; i++) {
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[end];
}
}
其它动态规划问题
10.正则表达式匹配
public class leetCode650 {
public int minSteps(int n) {
int[] dp = new int[n + 1];
int h=(int)Math.sqrt(n);
for (int i = 2; i <=n ; i++) {
dp[i]=i;
for (int j = 2; j <=h; j++) {
if (i%j==0){
dp[i]=dp[j]+dp[i/j];
break;
}
}
}
return dp[n];
}
}
91.解码方法
/**
* 如果要求前i个字符的解码数
* <p>
* 我们可以先求前i-1个字符的解码数,但前提条件是当前字符也可以解码(一个字符的话,只要不是0,都可以)
* 还可以求前i-2个字符的解码数,但前提条件是当前字符和前一个字符构成的两个数字是有效的。
* 看到这里大家应该已经明白了,如果没有条件限制的话,这题解法和爬楼梯完全一样,递归公式其实就是个斐波那契数列
* <p>
* dp[i]=dp[i-1]+dp[i-2]
* <p>
* 作者:sdwwld
* 链接:https://leetcode-cn.com/problems/decode-ways/solution/shu-ju-jie-gou-he-suan-fa-di-gui-he-dong-pnyf/
* 来源:力扣(LeetCode)
* 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
public class leetCode91 {
public int numDecodings(String s) {
int n = s.length();
s = " " + s;
char[] cs = s.toCharArray();
int[] f = new int[n + 1];
f[0] = 1;//空字符串也算一种
for (int i = 1; i <= n; i++) {
// a : 代表「当前位置」单独形成 item
// b : 代表「当前位置」与「前一位置」共同形成 item
int a = cs[i] - '0', b = (cs[i - 1] - '0') * 10 + (cs[i] - '0');
// 如果 a 属于有效值,那么 f[i] 可以由 f[i - 1] 转移过来
if (1 <= a && a <= 9) f[i] = f[i - 1];
// 如果 b 属于有效值,那么 f[i] 可以由 f[i - 2] 或者 f[i - 1] & f[i - 2] 转移过来
if (10 <= b && b <= 26) f[i] += f[i - 2];
}
return f[n];
}
}
343.整数拆分
package 动态规划;
public class leetCode343 {
public int integerBreak(int n) {
if (n<3) return n-1;
int[] dp = new int[n + 1];
dp[2]=1;
for (int i = 3; i <=n; i++) {
for (int j = 1; j < i; j++) {
dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
}
650.只有两个键的键盘
public class leetCode650 {
public int minSteps(int n) {
int[] dp = new int[n + 1];
int h=(int)Math.sqrt(n);
for (int i = 2; i <=n ; i++) {
dp[i]=i;
for (int j = 2; j <=h; j++) {
if (i%j==0){
dp[i]=dp[j]+dp[i/j];
break;
}
}
}
return dp[n];
}
}
一切皆可搜索
深度优先遍历
695.岛屿的最大面积
解题思路
本题和200. 岛屿数量相类似,可以先看200. 岛屿数量 我的题解,都是可以用 DFS 算法和 BFS 算法进行解决。下面是具体思路:
方法一:BFS 算法
BFS 算法也就是多叉树的层序遍历算法,我们找到一个为1的点(也就是岛屿)后,以这个节点为根节点,开始遍历多叉树,也就是把上下左右四个方向理解成为四个子节点。
需要注意理解的地方在于,遇到每一个岛屿(也就是数字1)开始使用 BFS 进行遍历时,实际上就是从这个点开始遍历一个多叉树(四叉树)
注意计算面积的位置,初始化面积是在 BFS 函数中,统计数量是在 for 循环中,因为每一次循环都是遍历添加到 q 的元素,也就是岛屿。
方法二:DFS 算法
DFS 算法的本质就是递归算法,也是需要把这个为1的节点抽象为多叉树的根节点,开始递归遍历。
在递归遍历的过程中,要使用一个变量记录更新该片岛屿的面积。
注意:
两种方法都用到了一个“沉岛”的思想,在遍历到一个岛屿后,把这个位置的1变成0,也就是类似于剪枝的操作,防止出现重复计算。
代码1 —— BFS 算法
class Solution {
}int maxArea = Integer.MIN_VALUE;
private int[][] dirs = new int[][] {{0,1}, {1,0}, {-1,0}, {0,-1}};
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
bfs(grid, i, j);
}
}
}
return maxArea == Integer.MIN_VALUE ? 0 : maxArea;
}
private void bfs(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
Queue<int[]> q = new LinkedList<>();
// 放入初始位置
q.offer(new int[]{i, j});
grid[i][j] = 0;
// 统计每一片岛屿的面积
int singArea = 0;
while (!q.isEmpty()) {
int sz = q.size();
for (int k = 0; k < sz; k++) {
int cur[] = q.poll();
singArea++;
// 横纵坐标
int x = cur[0], y = cur[1];
// 将上下左右的四个位置(子节点)放入队列中
// 注意索引不能越界
for (int[] d : dirs) {
int newX = x + d[0];
int newY = y + d[1];
if (newX >= 0 && newX < m && newY >= 0 && newY < n
&& grid[newX][newY] == 1) {
q.offer(new int[]{newX, newY});
grid[newX][newY] = 0;
}
}
}
}
maxArea = Math.max(maxArea, singArea);
}
}
代码2 —— DFS 算法
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length, n = grid[0].length;
int maxArea = Integer.MIN_VALUE; // 记录最大值
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
maxArea = Math.max(maxArea, dfs(grid, i, j));
}
}
}
return maxArea == Integer.MIN_VALUE ? 0 : maxArea;
}
private int dfs(int[][] grid, int i, int j) {
int m = grid.length, n = grid[0].length;
// base case
if (i < 0 || j < 0 || i >= m || j >= n
|| grid[i][j] == 0) {
return 0;
}
grid[i][j] = 0; // 沉岛,把1变成0
int area = 1;
// 递归遍历
area += dfs(grid, i - 1, j);
area += dfs(grid, i + 1, j);
area += dfs(grid, i, j - 1);
area += dfs(grid, i, j + 1);
作者:sui-ji-guo-cheng-sui-ji-guo
链接:https://leetcode-cn.com/problems/max-area-of-island/solution/dao-yu-de-zui-da-mian-ji-java-by-sui-ji-izbi5/
547.省份数量
public class leetCode547 {
public int findCircleNum(int[][] isConnected) {
int province = isConnected.length;
boolean[] visit = new boolean[province];
int cnt=0;
for (int i = 0; i < province; i++) {
if (visit[i]==false){
dfs(isConnected,i,visit);
cnt++;
}
}
return cnt;
}
private void dfs(int[][] isConnected,int i,boolean[] visit){
for (int j = 0; j < isConnected.length; j++) {
if (isConnected[i][j]==1&&visit[j]==false){
visit[j]=true;
dfs(isConnected,j,visit);
}
}
}
}
417.太平洋大西洋水流问题
https://leetcode-cn.com/problems/pacific-atlantic-water-flow/solution/shui-wang-gao-chu-liu-by-xiaohu9527-xxsx/
public class leetCode417 {
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) {
return new ArrayList<>();
}
int m = matrix.length;
int n = matrix[0].length;
int[][] pacific = new int[m][n];
int[][] atlantic = new int[m][n];
//从海洋边界开始
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 || j == 0) {
dfs(matrix, pacific, i, j, matrix[i][j]);
}
if (i == m - 1 || j == n - 1) {
dfs(matrix, atlantic, i, j, matrix[i][j]);
}
}
}
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (pacific[i][j] == 1 && atlantic[i][j] == 1) {
res.add(Arrays.asList(i, j));
}
}
}
return res;
}
private void dfs(int[][] matrix, int[][] aux, int i, int j, int pre) {
//判断边界
if (i < 0 || j < 0 || i > matrix.length - 1 || j > matrix[0].length - 1
//已经流到过了
|| aux[i][j] == 1
//不能流动
|| matrix[i][j] < pre) {
return;
}
aux[i][j] = 1;
dfs(matrix, aux, i - 1, j, matrix[i][j]);
dfs(matrix, aux, i + 1, j, matrix[i][j]);
dfs(matrix, aux, i, j - 1, matrix[i][j]);
dfs(matrix, aux, i, j + 1, matrix[i][j]);
}
}
130.被围绕的区域
判断O是否最终会被填充,只要这个O能沿着旁边的O到四条边就不会被填充。
所以我们可以从四条边上的O入手,将其改成K,然后遍历旁边的O 也改成K。
最终只要没有变成K的O将被填充,遍历数组把K改成O,其他的都改成X。
public class leetCode30 {
public void solve(char[][] board) {
int row=board.length;
int col = board[0].length;
for (int i = 0; i < row; i++) {
dfs(board,i,0);
dfs(board,i,col-1);
}
for (int j = 0; j < col; j++) {
dfs(board,0,j);
dfs(board,row-1,j);
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j]=='O') board[i][j]='X';
if (board[i][j]=='-') board[i][j]='O';
}
}
}
private void dfs(char[][] board, int i, int j) {
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != 'O') return;
board[i][j]='-';
dfs(board,i+1,j);
dfs(board,i-1,j);
dfs(board,i,j+1);
dfs(board,i,j-1);
}
}
回溯
46.全排列
# 回溯算法
回溯 ----递归1.递归的下面就是回溯的过程
2.回溯法是一个 纯暴力的 搜索
3.回溯法解决的问题:
3.1组合 如:1234 两两组合
3.2切割问题 如:一个字符串有多少个切割方式 ,或者切割出来是回文
3.3子集 : 1 2 3 4 的子集
3.4排列问题(顺序)
3.5棋盘问题:n皇后 解数独
4.回溯可抽象成树形结构
5.void backtracking(){
if(终止条件) {
收集结果
return
}
for(集合的元素集,类似子节点的个数)
{
处理结点
递归函数;
回溯操作
(撤销处理结点12, 2撤销 ,13 撤销3, 14)
}
}
public class leetCode46 {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
back(nums,0,nums.length-1,path,res);
return res;
}
private void back(int[] nums,int left,int right,List<Integer> path,List<List<Integer>> res){
if (left==right){
path=new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
path.add(nums[i]);
}
res.add(path);
return;
}
//在left到right的范围内去动态维护数组
for(int i=left;i<=right;i++){
swap(nums,left,i);//每次都进行交换,形成不同的
back(nums,left+1,right,path,res);
//在递归回退时,也要再进行一次交换
swap(nums,left,i);
}
}
private void swap(int[] nums,int i,int j){
int temp=nums[i];
nums[i]=nums[j];
nums[j]=temp;
}
}
47.全排列去重
//https://leetcode-cn.com/problems/permutations-ii/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liwe-2/
import java.util.*;
public class leetCode47 {
public List<List<Integer>> permuteUnique(int[] nums) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
if (len == 0) {
return res;
}
// 排序(升序或者降序都可以),排序是剪枝的前提
Arrays.sort(nums);
boolean[] used = new boolean[len];
// 使用 Deque 是 Java 官方 Stack 类的建议
Deque<Integer> path = new ArrayDeque<>(len);
dfs(nums, len, 0, used, path, res);
return res;
}
private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < len; ++i) {
if (used[i]) {
continue;
}
// 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义
// 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
path.addLast(nums[i]);
used[i] = true;
dfs(nums, len, depth + 1, used, path, res);
// 回溯部分的代码,和 dfs 之前的代码是对称的
used[i] = false;
path.removeLast();
}
}
}
77.组合
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
class Solution {
public List<List<Integer>> combine(int n, int k) {
List<List<Integer>> res = new ArrayList<>();
if (k <= 0 || n < k) {
return res;
}
// 从 1 开始是题目的设定
Deque<Integer> path = new ArrayDeque<>();
dfs(n, k, 1, path, res);
return res;
}
private void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
// 递归终止条件是:path 的长度等于 k
if (path.size() == k) {
res.add(new ArrayList<>(path));
return;
}
// 遍历可能的搜索起点
for (int i = begin; i <= n; i++) {
// 向路径变量里添加一个数
path.addLast(i);
// 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
dfs(n, k, i + 1, path, res);
// 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
path.removeLast();
}
}
}
40.组合去重
List<List<Integer>> res=new ArrayList<>();
int n=candidates.length;
if (n==0) return res;
Deque<Integer> path=new ArrayDeque<>();
int sum=0;
boolean[] visit=new boolean[n];
Arrays.sort(candidates);
backtracking(candidates,target,path,res,0,sum,visit);
return res;
}
private void backtracking(int[] candidates, int target, Deque<Integer> path,List<List<Integer>> res,int begin,int sum,boolean[] visit){
if (target==sum){
res.add(new ArrayList<>(path));
return;
}
for (int i = begin; i < candidates.length; i++) {
if(sum+candidates[i]<=target && !visit[i]){
//出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
if (i>0 &&candidates[i]== candidates[i-1]&&!visit[i-1]) continue;
visit[i]=true;
path.addLast(candidates[i]);
sum+=candidates[i];
backtracking(candidates,target,path,res,i+1,sum,visit);
path.removeLast();
sum-=candidates[i];
visit[i]=false;
}
}
79.字符串搜索
public class leetCode79 {
public boolean exist(char[][] board, String word) {
boolean[][] visit = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (backtracking(board,visit,word,i,j,0)) return true;
}
}
return false;
}
private boolean backtracking(char[][] board ,boolean[][] visit,String word,int i, int j,int index){
//递归终止条件
//如果所有字符串都匹配完了
if (index>=word.length()) return true;
//如果越界
if (i<0||j<0||i>=board.length||j>=board[0].length) return false;
//如果访问过
if (visit[i][j]==true) return false;
//如果不相等
if (word.charAt(index)!=board[i][j]) return false;
//将该点标记为访问
visit[i][j]=true;
//针对每个方向进行递归访问
if(backtracking(board,visit,word,i+1,j,index+1)) return true;
else if(backtracking(board,visit,word,i-1,j,index+1)) return true;
else if(backtracking(board,visit,word,i,j+1,index+1)) return true;
else if(backtracking(board,visit,word,i,j-1,index+1)) return true;
else {
回溯,将该点标记为未访问
visit[i][j]=false;
return false;
}
}
}
51.N皇后
public class leetCode51 {
List<List<String>> res = new ArrayList<>();
public List<List<String>> solveNQueens(int n) {
char[][] chessboard = new char[n][n];
for (char[] c : chessboard) {
Arrays.fill(c, '.');
}
backTrack(n, 0, chessboard);
return res;
}
public void backTrack(int n, int row, char[][] chessboard) {
if (row==n){
res.add(Array2List(chessboard));
return;
}
for (int col = 0; col < n; col++) {
if (isValid(row,col,n,chessboard)){
chessboard[row][col]='Q';
backTrack(n,row+1,chessboard);
chessboard[row][col]='.';
}
}
}
public List Array2List(char[][] chessboard) {
List<String> list=new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
public boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
for (int i = 0; i < row; i++) {
if (chessboard[i][col]=='Q') return false;
}
//判断左上角是否冲突
for (int i = row-1, j=col-1; i >=0&&j>=0 ; i--,j--) {
if (chessboard[i][j]=='Q') return false;
}
//判断右上角是否冲突
for (int i=row-1,j=col+1;i>=0&&j<=n-1;i--,j++){
if (chessboard[i][j]=='Q') return false;
}
return true;
}
}
37.解数独
solveSudokuHelper(board);
}
private boolean solveSudokuHelper(char[][] board){
//「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
// 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
for (int i = 0; i < 9; i++){ // 遍历行
for (int j = 0; j < 9; j++){ // 遍历列
if (board[i][j] != '.'){ // 跳过原始数字
continue;
}
for (char k = '1'; k <= '9'; k++){ // (i, j) 这个位置放k是否合适
if (isValidSudoku(i, j, k, board)){
board[i][j] = k;
if (solveSudokuHelper(board)){ // 如果找到合适一组立刻返回
return true;
}
board[i][j] = '.';
}
}
// 9个数都试完了,都不行,那么就返回false
return false;
// 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
// 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
}
}
// 遍历完没有返回false,说明找到了合适棋盘位置了
return true;
}
/**
* 判断棋盘是否合法有如下三个维度:
* 同行是否重复
* 同列是否重复
* 9宫格里是否重复
*/
private boolean isValidSudoku(int row, int col, char val, char[][] board){
// 同行是否重复
for (int i = 0; i < 9; i++){
if (board[row][i] == val){
return false;
}
}
// 同列是否重复
for (int j = 0; j < 9; j++){
if (board[j][col] == val){
return false;
}
}
// 9宫格里是否重复
int startRow = (row / 3) * 3;
int startCol = (col / 3) * 3;
for (int i = startRow; i < startRow + 3; i++){
for (int j = startCol; j < startCol + 3; j++){
if (board[i][j] == val){
return false;
}
}
}
return true;
作者:carlsun-2
链接:https://leetcode-cn.com/problems/sudoku-solver/solution/37-jie-shu-du-hui-su-sou-suo-suan-fa-xiang-jie-by-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
257.二叉树路径
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;
}
}
public class leetCode257 {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
if (res==null)return res;
List<Integer> path=new ArrayList<>();
backtracking(root,path,res);
return res;
}
//1.确定函数和参数
private void backtracking(TreeNode root,List<Integer> path,List<String> res){
path.add(root.val);
//2。终止条件
if (root.left==null&&root.right==null) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < path.size() - 1; i++) {
sb.append(path.get(i)).append("->");
}
sb.append(path.get(path.size()-1));
res.add(sb.toString());
return;
}
//3。确定单层递归逻辑
if (root.left!=null){
backtracking(root.left,path,res);
//回溯
path.remove(path.size()-1);
}
if (root.right!=null){
backtracking(root.right,path,res);
//回溯
path.remove(path.size()-1);
}
}
}
广度优先遍历
934.最短的桥
package 一切皆可搜索.广度优先遍历;
import java.util.ArrayDeque;
import java.util.Deque;
// 1、dfs找第一座岛屿,将第一座岛屿的所有元素置为2;
// 2、bfs找最短路径,从第一座岛屿的所有元素出发,一层层向外扩展,遇到第一个为1的元素停止,返回层数。
public class leetCode934 {
public int shortestBridge(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
boolean flag = false;
// dfs 找第一个岛屿
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
// 第一个值为1的
if (grid[row][col] == 1) {
// 遍历修改值为2
changeValue(grid, row, col, rows, cols);
flag = true;
break;
}
}
if (flag) {
break;
}
}
// bfs 找最短路径(一层层往外扩展)
Deque<Deque<int[]>> allDeque = new ArrayDeque<>();
Deque<int[]> queue = new ArrayDeque<>();
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
// 第一个值为1的
if (grid[row][col] == 2) {
queue.addLast(new int[]{row, col});
}
}
}
int count = 0;
allDeque.addLast(queue);
while (!allDeque.isEmpty()) {
// 保存下一层要遍历的元素
Deque<int[]> nextLevel = new ArrayDeque<>();
Deque<int[]> first = allDeque.pollFirst();
while (!first.isEmpty()) {
int[] ints = first.pollFirst();
int row = ints[0];
int col = ints[1];
// 四个方向
// 任何一个元素的相邻元素为1时,中断,返回最终结果
if (row + 1 >= 0 && row + 1 < rows && grid[row + 1][col] == 1) {
return count;
}
if (row - 1 >= 0 && row - 1 < rows && grid[row - 1][col] == 1) {
return count;
}
if (col + 1 >= 0 && col + 1 < cols && grid[row][col + 1] == 1) {
return count;
}
if (col - 1 >= 0 && col - 1 < cols && grid[row][col - 1] == 1) {
return count;
}
// 任何一个元素的相邻元素为0时,加入到待遍历的队列中
if (row + 1 >= 0 && row + 1 < rows && grid[row + 1][col] == 0) {
nextLevel.addLast(new int[]{row + 1, col});
grid[row + 1][col] = 2;
}
if (row - 1 >= 0 && row - 1 < rows && grid[row - 1][col] == 0) {
nextLevel.addLast(new int[]{row - 1, col});
grid[row - 1][col] = 2;
}
if (col + 1 >= 0 && col + 1 < cols && grid[row][col + 1] == 0) {
nextLevel.addLast(new int[]{row, col + 1});
grid[row][col + 1] = 2;
}
if (col - 1 >= 0 && col - 1 < cols && grid[row][col - 1] == 0) {
nextLevel.addLast(new int[]{row, col - 1});
grid[row][col - 1] = 2;
}
}
if (!nextLevel.isEmpty()) {
allDeque.add(nextLevel);
count++;
}
}
return count;
}
/**
* 深度优先遍历,标记其中一座岛为2
*/
private void changeValue(int[][] grid, int row, int col, int rows, int cols) {
if (row < 0 || row >= rows || col < 0 || col >= cols || grid[row][col] == 0 || grid[row][col] == 2) {
return;
}
grid[row][col] = 2;
changeValue(grid, row + 1, col, rows, cols);
changeValue(grid, row - 1, col, rows, cols);
changeValue(grid, row, col + 1, rows, cols);
changeValue(grid, row, col - 1, rows, cols);
}
}
310.最小高度树
public class FindMinHeightTrees {
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
List<Integer> res = new ArrayList<>();
/*如果只有一个节点,那么他就是最小高度树*/
if (n == 1) {
res.add(0);
return res;
}
/*建立各个节点的出度表*/
int[] degree = new int[n];
/*建立图关系,在每个节点的list中存储相连节点*/
List<List<Integer>> map = new ArrayList<>();
for (int i = 0; i < n; i++) {
map.add(new ArrayList<>());
}
for (int[] edge : edges) {
degree[edge[0]]++;
degree[edge[1]]++;/*出度++*/
map.get(edge[0]).add(edge[1]);/*添加相邻节点*/
map.get(edge[1]).add(edge[0]);
}
/*建立队列*/
Queue<Integer> queue = new LinkedList<>();
/*把所有出度为1的节点,也就是叶子节点入队*/
for (int i = 0; i < n; i++) {
if (degree[i] == 1) queue.offer(i);
}
/*循环条件当然是经典的不空判断*/
while (!queue.isEmpty()) {
res = new ArrayList<>();/*这个地方注意,我们每层循环都要new一个新的结果集合,
这样最后保存的就是最终的最小高度树了*/
int size = queue.size();/*这是每一层的节点的数量*/
for (int i = 0; i < size; i++) {
int cur = queue.poll();
res.add(cur);/*把当前节点加入结果集,不要有疑问,为什么当前只是叶子节点为什么要加入结果集呢?
因为我们每次循环都会新建一个list,所以最后保存的就是最后一个状态下的叶子节点,
这也是很多题解里面所说的剪掉叶子节点的部分,你可以想象一下图,每层遍历完,
都会把该层(也就是叶子节点层)这一层从队列中移除掉,
不就相当于把原来的图给剪掉一圈叶子节点,形成一个缩小的新的图吗*/
List<Integer> neighbors = map.get(cur);
/*这里就是经典的bfs了,把当前节点的相邻接点都拿出来,
* 把它们的出度都减1,因为当前节点已经不存在了,所以,
* 它的相邻节点们就有可能变成叶子节点*/
for (int neighbor : neighbors) {
degree[neighbor]--;
if (degree[neighbor] == 1) {
/*如果是叶子节点我们就入队*/
queue.offer(neighbor);
}
}
}
}
return res;/*返回最后一次保存的list*/
}
}
作者:xiao-xin-28
链接:https://leetcode-cn.com/problems/minimum-height-trees/solution/zui-rong-yi-li-jie-de-bfsfen-xi-jian-dan-zhu-shi-x/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
巧解数学问题
最大公因数:公共因子中最大的数
最小公倍数:公共倍数中最小的数||a*b/ 最大公因数
求最大公约数算法:
(1)辗转相除法
有两整数a和b:
① a%b得余数c
② 若c=0,则b即为两数的最大公约数
③ 若c≠0,则a=b,b=c,再回去执行①
质数(素数):除了1和本身没有其它公因子
204.计算质数
class Solution {
public int countPrimes(int n) {
boolean[] flag=new boolean[n];
int count=0;
for(int i=2;i<n;i++){
if(flag[i])
continue;
count++;
for(int j=i;j<n;j+=i){
flag[j]=true;
}
}
return count;
}
}
504.七进制数
class Solution {
public String convertToBase7(int num) {
if (num == 0) return "0";
boolean flag ;
flag = num > 0 ? false : true;
if (flag) num=-num;
String s="";
if (flag) s="-";
StringBuffer res=new StringBuffer();
while (num>0){
res.append(num%7);
num/=7;
}
if (flag) res.append("-");
return res.reverse().toString();
}
}
172.尾数0的个数
//1.一个末尾0的出现需要依靠2*5
//2.每两个数比如1,2,3,4...就会出现存有2因子的数
//3.每5个数才会出现存有5因子的数,比如1,2,3,4,5,6,7,8,9,10....
//4.也就是说找到5因子的个数总能找到2因子和它配对,问题变成了1...n中具有几个5因子
//5.需要注意某些数,比如25,125,625是存在多个5因子的,比如25有两个5因子,125有三个5因子,
// 其中25是每25个出现一次,125是125个出现一次....
//6.综上所述,每5个会有一个base的5因子,能够有a个25的就加a,能够有b个125的就加b....
public int trailingZeroes(int n) {
int zeroCount = 0;
int t;
while(n>0){
zeroCount+=(t=n/5);//这里就是变成了n/5,n/25,n/125的个数了,还节省了n/5前缀的计算
n = t;
}
return zeroCount;
}
public int trailingZeroes(int n) {
int count=0;
while(n>0){
count+=n/5;
n=n/5;
}
return count;
}
384.随机取样
package 巧解数学题;
import java.util.Random;
/*
isher-Yates 洗牌算法跟暴力算法很像。在每次迭代中,生成一个范围在当前下标到数组末尾元素下标之间的随机整数。
接下来,将当前元素和随机选出的下标所指的元素互相交换 - 这一步模拟了每次从 “帽子” 里面摸一个元素的过程,
其中选取下标范围的依据在于每个被摸出的元素都不可能再被摸出来了。此外还有一个需要注意的细节,
当前元素是可以和它本身互相交换的 - 否则生成最后的排列组合的概率就不对了
链接:https://leetcode-cn.com/problems/shuffle-an-array/solution/da-luan-shu-zu-by-leetcode/
*/
public class leetCode384 {
private int[] array;
private int[] original;
Random rand = new Random();
private int randRange(int min, int max) {
return rand.nextInt(max - min) + min;
}
private void swapAt(int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public leetCode384(int[] nums) {
array = nums;
original = nums.clone();
}
public int[] reset() {
array = original;
original = original.clone();
return original;
}
public int[] shuffle() {
for (int i = 0; i < array.length; i++) {
swapAt(i, randRange(i, array.length));
}
return array;
}
}
528.按权重随机选择
方法一、前缀和
今天这道题是非常的典型的随机权重问题,应用的场景非常多。
比如,我们经常说的负载均衡策略,同一个服务有多台服务器,一般来说有哪些策略这些服务器呢?
一般来说有:1. 轮询;2.随机;3.轮询+权重;4.随机+权重;5.一致性Hash;6.普通hash;7.最小连接数;8.最短响应时间;等等。
随机权重就是其中的一种。
我们先考虑3台服务器的情况,假设他们的权重分别为:[3, 5, 2],我们应该怎么来搞呢?
通常的做法,是生成一个10以内的随机数,如果这个随机数小于3,就选第一台机器;如果这个随机数小于8(3 + 5),就选第二台机器;否则,就选第三台。
这其实就是前缀和的思想,我们从第二台开始与随机数的比较是需要加上前面机器的权重的,这样更好计算。
同样地,这道题我们也可以这么来操作。
请看代码:
class Solution {
// 注意,Random是一个比较重的类,全局保留一个实例即可
// 或者使用 Math.random() 或 ThreadLocalRandom.nextInt() 方法
private Random random;
private int[] preSum;
public Solution(int[] w) {
this.random = new Random();
this.preSum = new int[w.length];
// 计算前缀和
this.preSum[0] = w[0];
for (int i = 1; i < w.length; i++) {
this.preSum[i] = this.preSum[i - 1] + w[i];
}
}
public int pickIndex() {
int max = this.preSum[this.preSum.length - 1];
// 注意,生成的随机数不能包含0,否则部分用例过不了
int rand = random.nextInt(max) + 1;
for (int i = 0; i < preSum.length; i++) {
// 判断rand的范围落在哪个区间
if (rand <= preSum[i]) {
return i;
}
}
return 0;
}
}
方法二、二分优化
方法一中,第二步查找的过程,我们是要查找生成的随机数在有序数组(前缀和是递增的)中的位置,所以,显然我们可以使用二分查找来加速。
请看代码:
class Solution {
private static Random random = new Random();
private int[] preSum;
public Solution(int[] w) {
this.preSum = new int[w.length];
this.preSum[0] = w[0];
for (int i = 1; i < w.length; i++) {
this.preSum[i] = this.preSum[i - 1] + w[i];
}
}
public int pickIndex() {
int max = this.preSum[this.preSum.length - 1];
int rand = random.nextInt(max) + 1;
return binarySearch(preSum, rand);
}
private int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = (left + right) / 2;
// 考虑等于的情况,去掉等号过不了所有用例
if (target <= nums[mid]) {
right = mid;
} else {
left = mid + 1;
}
}
return right;
}
}
时间复杂度:构造方法为 O(n)O(n),pickIndex()为 O(logn)O(logn)。
空间复杂度:O(n)O(n),前缀和需要额外的 O(n)O(n) 空间
链接:https://leetcode-cn.com/problems/random-pick-with-weight/solution/tong-ge-lai-shua-ti-la-yi-ti-liang-jie-q-ic4k/
382.链表的随机节点
思路解析:随机就要想到random函数,链表节点可以尝试把节点放进arraylist集合(arraylist、linkedlist、arrays.sort、hashmap、hashset这些法宝要记牢!)
class Solution {
List<Integer> list=new ArrayList();
Random random = new Random();
int i=0;
public Solution(ListNode head) {
while(head != null){
list.add(head.val);
head=head.next;
}
}
public int getRandom() {
int n = random.nextInt(list.size());
return list.get(n);
}
}
作者:upbeat-goodalllqy
链接:https://leetcode-cn.com/problems/linked-list-random-node/solution/lian-biao-sui-ji-jie-dian-javashi-xian-b-vj2f/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
168.EXCEL表列名
public class leetCode168 {
public String convertToTitle(int columnNumber) {
StringBuffer sb = new StringBuffer();
while (columnNumber>0){
columnNumber--;
sb.append((char)(columnNumber%26+'A'));
columnNumber/=26;
}
return sb.reverse().toString();
}
}
67.二进制求和
public class leetCode67 {
public String addBinary(String a, String b) {
StringBuilder ans = new StringBuilder();
int ca = 0; //是否进一位
for (int i = a.length() - 1, j = b.length() - 1; i >= 0 || j >= 0; i--, j--) {
int sum = ca;
sum += (i >= 0 ? a.charAt(i) - '0' : 0); // 获取字符串a对应的某一位的值 当i<0是 sum+=0(向前补0) 否则取原值 ‘1’的char类型和‘0’的char类型刚好相差为1
sum +=( j >= 0 ? b.charAt(j) - '0' : 0);// 获取字符串a对应的某一位的值 当i<0是 sum+=0(向前补0) 否则取原值 ‘1’的char类型和‘0’的char类型刚好相差为1
ans.append(sum % 2); //如果二者都为1 那么sum%2应该刚好为0 否则为1
ca = sum / 2; //如果二者都为1 那么ca 应该刚好为1 否则为0
}
ans.append(ca == 1 ? ca : "");// 判断最后一次计算是否有进位 有则在最前面加上1 否则原样输出
return ans.reverse().toString();
}
}
238.除自身以外的乘积
package 巧解数学题;
//原数组: [1 2 3 4]
//左部分的乘积: 1 1 1*2 1*2*3
//右部分的乘积: 2*3*4 3*4 4 1
//结果: 1*2*3*4 1*3*4 1*2*4 1*2*3*1
public class leetCode238 {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] left=new int[n];
left[0]=1;
left[1]=nums[0];
int[] right=new int[n];
right[n-1]=1;
int[]res=new int[n];
for (int i = 2; i < n; i++) {
left[i]=left[i-1]*nums[i-1];// 左边的元素乘积
}
for (int i = n-2; i >=0; i--) {
right[i]=right[i+1]*nums[i+1];//右边的乘积
}
for (int i = 0; i < n; i++) {
res[i]=left[i]*right[i];
}
return res;
}
}
462.最小移动数使数组相等
给出一个已经排好序的测试样例 [0, 1, 2, 6, 8]
通过对数据的观察,可以得知,对首尾的两个数 0,8 最小的移动次数就是在 [0, 8] 之间任意找一个数,他们的固定移动次数都是 8;如果尝试在这个区间外找一个数来计算移动次数,如找 -1,则 0和8 的移动次数则为 10
同理,我们对 1和6 进行最小次数移动的话, [1, 6] 中的任意数,他们固定移动 5次
最后剩下一个中间的数 2,不移动的话,最小次数为 0
对这个参考数的选取则为 [0, 8] ∪ [1, 6] ∪ [2] = 2,他们的最小移动次数就是 8+5+0 = 13
上述思路可以确定,本题的核心点就是寻找中位数,上面分析的是奇数数组,下面分析偶数数组
示例: [0, 1, 2, 6]
1、在 [0, 6] 任意找一个数,固定最小次数 6
2、在 [1, 2] 任意找一个数,固定最小次数 1
中间数的选取条件为 [0, 6] ∪ [1, 2] = [1, 2],即 1或2 都行,最小移动次数为 6+1 = 7
public class leetCode462 {
public int minMoves2(int[] nums) {
Arrays.sort(nums);
int mid=nums.length/2;
int res=0;
for (int i = 0; i < nums.length; i++) {
res+=Math.abs(nums[i]-nums[mid]);
}
return res;
}
}
453.最小操作使数组相等
package 巧解数学题;
//正着需要操作n-1个加1,反着只需要减一个1,嗯,我是sb
public class leetCode453 {
public int minMoves(int[] nums) {
int min=nums[0];
for (int i = 1; i < nums.length; i++) {
min=Math.min(min,nums[i]);
}
int res=0;
for (int i = 0; i < nums.length; i++) {
res+=Math.abs(nums[i]-min);
}
return res;
}
}
169.多数元素
import java.util.Arrays;
//摩尔投票法思路
// 候选人(cand_num)初始化为nums[0],票数count初始化为1。
// 当遇到与cand_num相同的数,则票数count = count + 1,否则票数count = count - 1。
// 当票数count为0时,更换候选人,并将票数count重置为1。
// 遍历完数组后,cand_num即为最终答案。
//
// 为何这行得通呢?
// 投票法是遇到相同的则票数 + 1,遇到不同的则票数 - 1。
// 且“多数元素”的个数> ⌊ n/2 ⌋,其余元素的个数总和<= ⌊ n/2 ⌋。
// 因此“多数元素”的个数 - 其余元素的个数总和 的结果 肯定 >= 1。
// 这就相当于每个“多数元素”和其他元素 两两相互抵消,抵消到最后肯定还剩余至少1个“多数元素”。
// 无论数组是1 2 1 2 1,亦或是1 2 2 1 1,总能得到正确的候选人。
public class leetCode169 {
public int majorityElement(int[] nums) {
int countNum=nums[0];
int count=1;
for (int i = 1; i < nums.length; i++) {
if (nums[i]==countNum) count++;
else {
count--;
if (count==0){
countNum=nums[i];
count=1;
}
}
}
return countNum;
}
}
202.快乐数
使用 “快慢指针” 思想,找出循环:“快指针” 每次走两步,“慢指针” 每次走一步,当二者相等时,即为一个循环周期。此时,判断是不是因为 1 引起的循环,是的话就是快乐数,否则不是快乐数。
注意:此题不建议用集合记录每次的计算结果来判断是否进入循环,因为这个集合可能大到无法存储;另外,也不建议使用递归,同理,如果递归层次较深,会直接导致调用栈崩溃。不要因为这个题目给出的整数是 int 型而投机取巧。
public int squareSum(int n) {
int sum = 0;
while(n > 0){
int digit = n % 10;
sum += digit * digit;
n /= 10;
}
return sum;
}
public boolean isHappy(int n) {
int slow = n, fast = squareSum(n);
while (slow != fast){
slow = squareSum(slow);
fast = squareSum(squareSum(fast));
};
return slow == 1;
}
位运算
462.汉明距离
// 右移统计
// 每次都统计当前 xx 和 yy 的最后一位,统计完则将 xx 和 yy 右移一位。
// 当 xx 和 yy 的最高一位 11 都被统计过之后,循环结束
public class leetCode462 {
public int hammingDistance(int x, int y) {
int res=0;
while((x|y)!=0){
int a=x&1, b=y&1;
res+=a^b;
x>>=1;
y>>=1;
}
return res;
}
}
逐位比较
本身不改变 xx 和 yy,每次取不同的偏移位进行比较,不同则加一。
循环固定取满 3232 。
public int hammingDistance(int x, int y) {
int ans = 0;
for (int i = 0; i < 32; i++) {
int a = (x >> i) & 1 , b = (y >> i) & 1;
ans += a ^ b;
}
return ans;
}
190.颠倒二进制
//每次循环的时候把n的最后一位数字(二进制的)截取掉,放到一个新的数字中的末尾
public class leetCode190 {
public int reverseBits(int n) {
int a = 0; //首先初始化一个存放结果的整数a
for(int i =0;i<=31;i++){
a <<= 1;
a += n & 1;
n >>= 1; //首先n>>i表示每次n向右移一位
//1&一个二进制数表示的是取出该二进制数的最后一位
//将最后一位取出以后,向左移动31-i位,将其加入到结果中即可。多次循环,最终可以得到目标值。
}
return a;
}
public int reverseBits(int n) {
int a = 0; //首先初始化一个存放结果的整数a
for(int i =0;i<=31;i++){
a+=((1&(n>>i))<<(31-i)); //首先n>>i表示每次n向右移一位
//1&一个二进制数表示的是取出该二进制数的最后一位
//将最后一位取出以后,向左移动31-i位,将其加入到结果中即可。多次循环,最终可以得到目标值。
}
return a;
}
}
7.整数反转
int 类型的取值范围就是【-2^31,2^31-1】,所以要判断反转后是否溢出(超过32位),可以先用一个long类型的保存该数,把它强转成 int 类型。long强转int 的原理就是截取long类型数据的后32。强转(截取)后和原数不相等证明溢出。
long ans=0; //注意是long类型
while (x!=0){
ans=ans*10+x%10;
x=x/10;
}
return (int)ans==ans?(int)ans:0;
136.只出现一次的数字
使用异或性质完成题设
01.异或性质1:任何数值异或0等于数值它本身
02.异或性质2:两个数值的异或,相同为0,相异为1
03.异或性质3:多个数值的异或满足交换律和结合律
根据异或的三个性质,当数组中的数值挨个异或,最终留下的结果就是我们所需的数字。
public class leetCode136 {
public int singleNumber(int[] nums) {
int res=0;
for (int num : nums) {
res^=num;
}
return res;
}
}
260.只出现1次的数字二?
class Solution {
public int[] singleNumber(int[] nums) {
int temp = 0;
//求出异或值
for (int x : nums) {
temp ^= x;
}
//保留最右边的一个 1
int group = temp & (-temp);
System.out.println(group);
int[] arr = new int[2];
for (int y : nums) {
//分组位为0的组,组内异或
if ((group & y) == 0) {
arr[0] ^= y;
//分组位为 1 的组,组内异或
} else {
arr[1] ^= y;
}
}
return arr;
}
}
作者:yuan-chu-de-suan-fa-xiao-wu
链接:https://leetcode-cn.com/problems/single-number-iii/solution/dong-hua-tu-jie-yi-ding-neng-hui-by-yuan-gqg8/
137.只出现一次的数字三
class Solution {
public int singleNumber(int[] nums) {
int a = 0,b = 0;
for (int num : nums) {
a ^= num & ~b;
b ^= num & ~a;
}
return a;
}
}
作者:qian-yang-d
链接:https://leetcode-cn.com/problems/single-number-ii/solution/qian-yang-dfayou-xian-zi-dong-ji-xiang-j-ye0v/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
342.4的幂
class Solution {
public boolean isPowerOfFour(int n) {
for (int i = 0; i < 32; i+=2) {
if ((1<<i)==n) return true;
}
return false;
}
}
318.最大单词长度的乘积
public int maxProduct(String[] words) {
//思路:切入点是如何快速的判断两个字符串是否有字符重复?有一种技巧,为每一个串开辟一个int,每一个位代表一个字符
// 把所有的字符都记录到int上后,只需要把两个int进行&运算,就可以知道是否存在重复的字符。因为:如果有都为1的位,
// 那么结果肯定不为0,所以当结果为0时,说明不存在重复的字符,这个时候就可以进行乘积的计算。
// 字符串数组的个数
int n = words.length;
// 每一个串 对应一个int 每一个串的一个字符,对应二进制的一位,比如第一个串ab 对应00011,a对应0001等
int[] masks = new int[n];
// 串的长度
int[] lens = new int[n];
// 临时变量,记录这个串上面有的字母
int bitmask = 0;
// 遍历字符串数组,每个串的字符记录到masks数组中
for (int i = 0; i < n; ++i) {
bitmask = 0;// 初始化临时变量
// 把字符串的每一个字符转记录到int的某一位
for (char ch : words[i].toCharArray()) {
// 通过函数或者字符要记录在int的哪一位上,然后通过左移,把这个位标记为1
// 然后与原临时进行 |运算,|运算会保留原来是1的是,记录不会丢失,有的话还是有,没有的话就加上
bitmask |= 1 << (ch-'a');
}
// 把记录好的tmp保存到数组中,代表一个字符串,
masks[i] = bitmask;
// 记录长度,方便后面的乘积运算
lens[i] = words[i].length();
}
// 乘积的最大长度,默认是0
int maxVal = 0;
// 遍历代表字符串的int数组,与后面的串所代表的int进行判断,没有重复就求乘积
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
// 映射后进行 & 运算,如果所有位置都没有重复,那么结果会是零
if ((masks[i] & masks[j]) == 0)
// 计算乘积,选择大的
maxVal = Math.max(maxVal, lens[i] * lens[j]);
return maxVal;
}
338.比特位计数
x&1==0 等价 x%2==0
对于所有的数字,只有两类:
奇数:二进制表示中,奇数一定比前面那个偶数多一个 1,因为多的就是最低位的 1。
举例:
0 = 0 1 = 1
2 = 10 3 = 11
偶数:二进制表示中,偶数中 1 的个数一定和除以 2 之后的那个数一样多。因为最低位是 0,除以 2 就是右移一位,也就是把那个 0 抹掉而已,所以 1 的个数是不变的。
举例:
2 = 10 4 = 100 8 = 1000
3 = 11 6 = 110 12 = 1100
另外,0 的 1 个数为 0,于是就可以根据奇偶性开始遍历计算了。
public class leetCode338 {
public int[] countBits(int n) {
int[] res = new int[n + 1];
for (int i = 1; i <= n; i++) {
if ((i&1)==0) res[i]=res[i>>1];
else res[i]=res[i-1]+1;
}
return res;
}
}
48.旋转图像
class Solution {
public void rotate(int[][] matrix) {
int n = matrix.length;
//先沿斜对角线翻转
for(int i = 0;i < n;i ++)
for(int j = 0;j < i;j ++){
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
//再沿垂直竖线翻转
for(int i = 0;i < n;i ++)
for(int j = 0, k = n - 1; j < k ; j++, k--){
int temp = matrix[i][k];
matrix[i][k] = matrix[i][j];
matrix[i][j] = temp;
}
}
}
268.缺失的数据
异或运算可以解决出现1次的数据和缺失的数据
public class leetCode268 {
public int missingNumber(int[] nums) {
int res=0;
int n = nums.length;
for (int i = 0; i <=n; i++) {
res^=i;
}
for (int num : nums) {
res^=num;
}
return res;
}
}
693.交替位二进制数
class Solution {
public boolean hasAlternatingBits(int n) {
// 初始化,为 n 的最后一位
int prev = n & 1;
while (n != 0) {
n >>= 1;
int cur = n & 1;
// 当前和上一个相等,返回 false
if (cur == prev) return false;
// 不相等,把 prev 设置当前,用于下一个比较
prev = cur;
}
// 遍历完,都不相等,返回 true
return true;
}
}
476.数字的补码
class Solution {
public int findComplement(int num) {
int res=0;int i=0;
while (num!=0){
if((num&1)==0)
res+=1<<i;
num>>=1;
i++;
}
return res;
}
}
字符串
字符串比较
242.有效的字母异位词
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length()!=t.length()) return false;
int []res=new int[26];
for(int i=0;i<s.length();i++){
res[s.charAt(i)-'a']++;
res[t.charAt(i)-'a']--;
}
for(int i=0;i<26;i++){
if(res[i]!=0) return false;
}
return true;
}
}
205.同构字符串
即,按照字母出现的顺序,把两个字符串都映射到另一个集合中。
举个现实生活中的例子,一个人说中文,一个人说法语,怎么判断他们说的是一个意思呢?把中文翻译成英语,把法语也翻译成英语,然后看最后的英语是否相同即可
public boolean isIsomorphic(String s, String t) {
int n = s.length();
int[] mapS = new int[128];
int[] mapT = new int[128];
for (int i = 0; i < n; i++) {
char c1 = s.charAt(i);
char c2 = t.charAt(i);
//当前的映射值是否相同
if (mapS[c1] != mapT[c2]) {
return false;
} else {
//是否已经修改过,修改过就不需要再处理
if (mapS[c1] == 0) {
mapS[c1] = i + 1;
mapT[c2] = i + 1;
}
}
}
return true;
}
647.回文子串
class Solution6472 {
public int countSubstrings(String s) {
// 中心扩展法
int ans = 0;
for (int center = 0; center < 2 * s.length() - 1; center++) {
// left和right指针和中心点的关系是?
// 首先是left,有一个很明显的2倍关系的存在,其次是right,可能和left指向同一个(偶数时),也可能往后移动一个(奇数)
// 大致的关系出来了,可以选择带两个特殊例子进去看看是否满足。
int left = center / 2;
int right = left + center % 2;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
ans++;
left--;
right++;
}
}
return ans;
}
}
作者:jawhiow
链接:https://leetcode-cn.com/problems/palindromic-substrings/solution/liang-dao-hui-wen-zi-chuan-de-jie-fa-xiang-jie-zho/
5.子系列最长回文串
public class leetCode5 {
public String longestPalindrome(String s) {
// ababa 求最长公共子串
int len = s.length();
String result = "";
for (int i = 0; i < len * 2 - 1; i++) {
int left = i / 2;
int right = left + i % 2;
while (left >= 0 && right < len && s.charAt(left) == s.charAt(right)) {
String tmp = s.substring(left, right + 1);
if (tmp.length() > result.length()) {
result = tmp;
}
left--;
right++;
}
}
return result;
}
}
409.最长回文串
public class leetCode409 {
public int longestPalindrome(String s) {
if(s.length()==0) return 0;
int[] arr=new int[128];
for (int i = 0; i < s.length(); i++) {
arr[s.charAt(i)]++;
}
int sum=0;
int n=0;
for (int i : arr) {
if (i%2==0) sum+=i;
else {
sum=sum+i-1;
n++;
}
}
if (n>=1) return sum+1;
return sum;
}
}
3.无重复字符串的最长子串
import java.util.HashMap;
/*
start不动,end向后移动
当end遇到重复字符,start应该放在上一个重复字符的位置的后一位,同时记录最长的长度
怎样判断是否遇到重复字符,且怎么知道上一个重复字符的位置?--用哈希字典的key来判断是否重复,用value来记录该字符的下一个不重复的位置。
*/
public class leetCode3 {
public int lengthOfLongestSubstring(String s) {
int maxLen=0;
HashMap<Character, Integer> map = new HashMap<>();
int left=0,right=0;
while (right<s.length()){
if (map.containsKey(s.charAt(right))) left=Math.max(left,map.get(s.charAt(right))+1);
map.put(s.charAt(right),right);
maxLen=Math.max(maxLen,right-left+1);
right++;
}
return maxLen;
}
}
696.计数二进制子串
public int countBinarySubstrings(String s) {
if (s.length() == 1) return 0;
char[] arr = s.toCharArray();
int cnt = 0, a = 1, b = 0; // arr[0] 算入 a 手
for (int i = 1; i < arr.length; i++) {
if (arr[i] == arr[i - 1]) {
a++; // 同类入 a
} else {
cnt += Math.min(a, b); // 计数
b = a; // 倒腾 a -> b
a = 1; // 新的计入 a
}
}
cnt += Math.min(a, b); // 虽跳出循环,但手中有物,也要计数
return cnt;
}
作者:lzhlyle
链接:https://leetcode-cn.com/problems/count-binary-substrings/solution/java-liang-shou-lun-liu-dao-teng-by-lzhlyle-2/
字符串理解
227.基本计算器
import java.util.LinkedList;
public class leetCode227 {
public int calculate(String s) {
LinkedList<Integer> stackInt = new LinkedList<>();
LinkedList<Character> stackChar = new LinkedList<>();
int index = 0;
while (index < s.length()) {
if (s.charAt(index) != ' ') {
int num = 0;
//添加数字
if (s.charAt(index) >= '0' && s.charAt(index) <= '9') {
while (index < s.length() && s.charAt(index) >= '0' && s.charAt(index) <= '9') {
num = num * 10 + s.charAt(index) - '0';
index++;
}
//先计算乘除
if (!stackChar.isEmpty() && (stackChar.peek() == '/' || stackChar.peek() == '*')) {
int last = stackInt.pop();
if (stackChar.peek() == '/') {
num = last / num;
} else {
num = last * num;
}
stackChar.pop();
}
stackInt.push(num);
continue;
} else {
//添加运算符
stackChar.push(s.charAt(index));
index++;
}
} else {
index++;
}
}
//最后计算两个栈里面的内容,里面只有加减了,按加减算
int res = stackInt.pollLast();
while (!stackChar.isEmpty()) {
char c = stackChar.pollLast();
int cur = stackInt.pollLast();
if (c == '+') {
res += cur;
} else {
res -= cur;
}
}
return res;
}
}
772.基本计算器二
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
public class leetCode772 {
public int calculate(String s) {
//规定优先级
HashMap<Character, Integer> prio = new HashMap<>();
prio.put('(',1);
prio.put('*',2);
prio.put('/',2);
prio.put('+',3);
prio.put('-',3);
Deque<Long> stackNum = new ArrayDeque<>();
Deque<Character> stackChar = new ArrayDeque<>();
for (int i = 0; i < s.length(); i++) {
char c=s.charAt(i);
if (c=='('){
stackChar.push(c);
}else if (Character.isDigit(c)){
//截取数字
int j=i;
while (j<s.length()&&Character.isDigit(s.charAt(j))) j++;
stackNum.push(Long.parseLong(s.substring(i,j+1)));
i=j-1;
}else if (c==')'){
while (stackChar.peek()!='(') calc(stackNum,stackChar);
}else if (c!=' '){
while (!stackChar.isEmpty()&&stackChar.peek()>=prio.get(c)) calc(stackNum,stackChar);
}
stackChar.push(c);
}
while (stackNum.size()>1){
calc(stackNum,stackChar);
}
long res=stackNum.peek();
return (int)res;
}
private void calc(Deque<Long> stackNum, Deque<Character> stackChar) {
long n2=stackNum.pop();
long n1=stackNum.pop();
switch (stackChar.pop()){
case '+':stackNum.push(n1+n2);break;
case '-':stackNum.push(n1-n2);break;
case '*':stackNum.push(n1*n2);break;
case '/':stackNum.push(n1/n2);break;
}
}
}
字符串匹配
28.实现strStr()
//https://leetcode-cn.com/problems/implement-strstr/solution/shua-chuan-lc-shuang-bai-po-su-jie-fa-km-tb86/
public class leetCode28 {
//KMP算法不会?截取haystack中长度为needle的子串并进行比较
public int strStr(String haystack, String needle) {
if(needle=="") return 0;
int m=haystack.length(),n=needle.length();
for (int i = 0; i <m-n+1 ; i++) {
String str=haystack.substring(i,i+n);
if (str.equals(needle)) return i;
}
return -1;
}
}
巧用数据结构
数组
448.找到数组中缺失的数字
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> res=new ArrayList<>();
int[] map = new int[nums.length + 1];
for (int num : nums) {
map[num]++;
}
for (int i = 1; i <= map.length; i++) {
if (map[i]==0) res.add(i);
}
return res;
}
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
//用来存放结果
List<Integer> res = new ArrayList<>();
//1. 遍历下数组的元素,对对应的索引位置的元素作标记
int len = nums.length;
for(int i = 0; i < len; i++){
int num = Math.abs(nums[i]); //由于数组的元素有可能被*-1,所以取绝对值
int index = num - 1;
if(nums[index] > 0){
nums[index] *= -1;
}
}
// 寻找没有标记的索引位置
for(int i = 0; i < len; i++){
if(nums[i] > 0){
int num = i + 1; //将索引转化为对应的元素
res.add(num);
}
}
return res;
}
}
作者:xmblgt
链接:https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/solution/bu-xu-yao-e-wai-kong-jian-si-lu-chao-ji-qing-xi-bu/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
287.寻找重复数
public class leetCode287 {
public int findDuplicate(int[] nums) {
int n=nums.length;
int[] res = new int[n];
for (int num : nums) {
int index=num-1;
res[index]++;
}
for (int i = 0; i < n; i++) {
if (res[i]>=2) return i+1;
}
return -1;
}
}
769.最多能完成排序的块
在本题中,容易发现可以进行分块的情况:
即从0到当前下标i的元素也是一个[0, 1, ..., i-1]的全排列数组时,则从下标i处可以进行分块
为此,可以设置max_value用以记录到i为止数组的最大值,用count记录分块数。则当i=max_value时,数组从0到i的元素则构成一个满足要求的全排列,即可进行分块
复杂度分析:
时间复杂度:O(n)
空间复杂度:O(1)
代码
class Solution {
public int maxChunksToSorted(int[] arr) {
int count=0, max_value=0;
for(int i=0;i<arr.length;i++){
max_value = Math.max(max_value, arr[i]);
if(i == max_value)
count++;
}
return count;
}
}
作者:yxq-5
链接:https://leetcode-cn.com/problems/max-chunks-to-make-sorted/solution/qiao-yong-shu-zu-xia-biao-jin-xing-fen-k-56me/
240.搜索二维矩阵
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 && matrix[0].length == 0) return false;
int i = 0, j = matrix[0].length - 1; //矩阵右上角
while(i < matrix.length && j >= 0)
{
if(matrix[i][j] == target) return true;
else if( matrix[i][j] < target) i++; //排除一行
else if( matrix[i][j] > target) j--; //排除一列
}
return false;
}
}
566.重塑矩阵
class Solution {
public int[][] matrixReshape(int[][] nums, int r, int c) {
int m = nums.length, n = nums[0].length;
// 特殊情况判断
if (m * n != r * c) {
return nums;
}
// 双重循环 + 数组元素位置
// 例:原5*7数组中的一个元素a[2][4],要放在新的7*5数组的什么位置??
// a[2][4] = 2 * 7 + 4 = 18
// 按行遍历的话
// 所以他应该在新数组的中得位置:b[18 / 5]][18 % 5] = b[3][3] => 3*5+3=18
int[][] resArr = new int[r][c];
int count = 0;// 表示按行遍历时,在数组中的第几个位置
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
resArr[i][j] = nums[count / n][count % n];
count++;
}
}
return resArr;
}
}
栈和队列
232.用栈实现队列
package 巧用数据结构.栈和队列;
import java.util.Stack;
public class leetCode232 {
class MyQueue {
Stack<Integer> in=null;
Stack<Integer> out=null;
public MyQueue() {
in=new Stack<>();
out=new Stack<>();
}
public void push(int x) {
while (!out.isEmpty()){
in.push(out.pop());
}
in.push(x);
}
public int pop() {
while (!in.isEmpty()){
out.push(in.pop());
}
return out.pop();
}
public int peek() {
while (!in.isEmpty()){
out.push(in.pop());
}
return out.peek();
}
public boolean empty() {
if (in.isEmpty()&&out.isEmpty()) return true;
else return false;
}
}
}
155.最小栈
public class leetCode155 {
class MinStack {
Stack<Integer> stack=null;
Stack<Integer> stackMin=null;
public MinStack() {
stack=new Stack<>();
stackMin=new Stack<>();
}
public void push(int val) {
if (stackMin.isEmpty()||stackMin.peek()>=val) stackMin.push(val);
stack.push(val);
}
public void pop() {
int n=stack.pop();
if (stackMin.peek()==n) stackMin.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return stackMin.peek();
}
}
}
20.有效的括号
public class leetCode20 {
public boolean isValid(String s) {
Stack<Character> stack=new Stack<>();
for (int i = 0; i < s.length(); i++) {
char ch=s.charAt(i);
if (ch=='('||ch=='['||ch=='{') stack.push(ch);
else {
if (!stack.isEmpty()){
char c=stack.pop();
if (ch==')'&&c!='(') return false;
else if (ch==']'&&c!='[') return false;
else if (ch=='}'&&c!='{') return false;
}else{
return false;
}
}
}
return stack.isEmpty();
}
}
225.用队列实行栈
class MyStack {
private Queue<Integer> queue;
//初始化数据结构
public MyStack() {
queue = new ArrayDeque<>();
}
//添加元素到栈底
//每次将新加入的队尾元素循环放入到队头
public void push(int x) {
int n = queue.size();
queue.offer(x);
for(int i = 0;i < n;i++){
queue.offer(queue.poll());//注意队列中删除队头为poll()方法
}
}
//删除栈顶元素并返回
public int pop() {
return queue.poll();
}
//返回栈顶元素
public int top() {
return queue.peek();
}
//判断栈是否为空
public boolean empty() {
return queue.isEmpty();
}
}
单调栈
对于找最近一个比当前值大/小」的问题,都可以使用单调栈来解决⌟
503.下一个更大的元素
public int[] nextGreaterElements(int[] nums) {
int length = nums.length;
int res[] = new int[length];
Arrays.fill(res, -1);//默认都为-1
Stack<Integer> stack = new Stack<>();
//相当于把数组循环两遍
for (int i = 0; i < length * 2; i++) {
//遍历数组的第index(index从0开始)个元素,因为数组会遍历
//两遍,会导致数组越界异常,所以这里要对数组长度进行求余
int index = i % length;
//单调栈,他存储的是元素的下标,不是元素具体值,从栈顶
//到栈底所对应的值是递增的(栈顶元素在数组中对应的值最小,
//栈底元素对应的值最大),如果栈顶元素对应的值比nums[index]小,
//说明栈顶元素对应的值遇到了右边第一个比他大的值,然后栈顶元素出栈,
//让他对应的位置变为nums[index],也就是他右边第一个比他大的值,
//然后继续判断……
while (!stack.isEmpty() && nums[stack.peek()] < nums[index])
res[stack.pop()] = nums[index];
//当前元素的下标入栈
stack.push(index);
}
return res;
}
739.单调栈
public int[] dailyTemperatures(int[] T) {
int[] result = new int[T.length];
// 根据题目要求如果之后气温不再升高
// 该位置用0代替,因此默认值设置为0
Arrays.fill(result,0);
Stack<Integer> stack = new Stack<>();
// 开始正向遍历
for(int i = 0; i < T.length; i++) {
// 栈不为空且当前索引对应的温度大于栈顶索引对应的温度
// 说明当前索引对应的温度是比栈顶索引对应的温度更高
// while循环表示:
// 当前索引对应的温度比栈中已存索引对应温度高时,则栈顶元素出栈
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
// 栈顶索引出栈
int prevIndex = stack.pop();
// 当前索引与栈顶索引的差值表示的就是需要等待的天数
result[prevIndex] = i-prevIndex;
}
// 将当前考察温度的索引入栈,看后面是否有比其高的温度
stack.push(i);
}
return result;
}
作者:hardcore-aryabhata
链接:https://leetcode-cn.com/problems/daily-temperatures/solution/dong-hua-yan-shi-dan-diao-zhan-739mei-ri-iita/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
优先队列
23.合并K个有序链表
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists==null || lists.length==0) {
return null;
}
//创建一个堆,并设置元素的排序方式
PriorityQueue<ListNode> queue = new PriorityQueue(new Comparator<ListNode>() {
public int compare(ListNode o1, ListNode o2) {
return (o1.val - o2.val);
}
});
//遍历链表数组,然后将每个链表的每个节点都放入堆中
for(int i=0;i<lists.length;i++) {
while(lists[i] != null) {
queue.add(lists[i]);
lists[i] = lists[i].next;
}
}
ListNode dummy = new ListNode(-1);
ListNode head = dummy;
//从堆中不断取出元素,并将取出的元素串联起来
while( !queue.isEmpty() ) {
dummy.next = queue.poll();
dummy = dummy.next;
}
dummy.next = null;
return head.next;
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/duo-tu-yan-shi-23-he-bing-kge-pai-xu-lian-biao-by-/
313.超级丑数
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
public class leetCode313 {
public int nthSuperUglyNumber(int n, int[] primes) {
PriorityQueue<Long> queue=new PriorityQueue<>();
Set<Long>set=new HashSet<>();
queue.add(1l);
set.add(1l);
long res=1;
for (int i = 0; i < n; i++) {
res=queue.poll();
for (int prime : primes) {
if (set.add(prime*res))queue.add(prime*res);
}
}
return (int) res;
}
}
870.优势洗牌
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
//田忌赛马~
int n =nums1.length;
//大顶堆
PriorityQueue<int[]> maxqueue = new PriorityQueue<>(new Comparator<int[]>()
{
public int compare(int[] pair1 , int[] pair2)
{
return pair2[1] - pair1[1]; //降序排序
}
});
for(int i = 0; i < n; i++)
{
maxqueue.offer(new int[]{i , nums2[i]});
}
Arrays.sort(nums1);
int left = 0, right = n - 1; //left指向最小值,right指向最大值
int[] res = new int[n];
while(!maxqueue.isEmpty())
{
int[] tmp = maxqueue.poll();
int i = tmp[0] , val = tmp[1];
if(val < nums1[right])
{
res[i] = nums1[right];
right--;
}else{
//无论如何比不过,减小损失
res[i] = nums1[left];
left++;
}
}
return res;
}
}
作者:chuanhong
链接:https://leetcode-cn.com/problems/advantage-shuffle/solution/tian-ji-sai-ma-by-hellohake-0orv/
218.天际线问题?
public class Solution {
public List<List<Integer>> getSkyline(int[][] buildings) {
List<List<Integer>> res=new ArrayList<>();
int cur_x,cur_h,i=0,len=buildings.length;
//先根据高度降序,再根据右端点升序进队
//目的是为了获取拔高了天际线且妨碍到前一个建筑物的建筑
//优先队列存储的是数组,为[height,right]
PriorityQueue<int[]> priorityQueue=new PriorityQueue<>(new Comparator<int[]>() {
public int compare(int[] x, int[] y) {
if (x[0]!=y[0]) {
return y[0]-x[0];
} else {
return x[1]-y[1];
}
}
});
while (!priorityQueue.isEmpty()||i<len) {
//队列为空有两种情况,一是刚开始,二是两部分建筑无重叠前面一部分已经全部出队
//或者当前建筑的左端点小于等于队顶建筑右端点,即遮挡到它了
if (priorityQueue.isEmpty()||i<len&&buildings[i][0]<=priorityQueue.peek()[1]) {
//获取当前建筑的左端点并入队
cur_x=buildings[i][0];
//如果几个建筑左端点一样同样入队
while (i<len&&cur_x==buildings[i][0]) {
priorityQueue.offer(new int[]{buildings[i][2],buildings[i][1]});
i++;
}
} else {
//另一种情况,当前建筑未遮挡前面部分建筑,造成了空隙
//获取之前这部分最高建筑的右端点
cur_x=priorityQueue.peek()[1];
//同时将其左边的(比较右端点)包括它自己出队
while (!priorityQueue.isEmpty()&&priorityQueue.peek()[1]<=cur_x) {
priorityQueue.poll();
}
}
//如果队里还有建筑那么该队顶建筑的高度+之前获取的左或右端点即为一个关键点
cur_h=priorityQueue.isEmpty()?0:priorityQueue.peek()[0];
//如果跟前面一个元素一样水平高度,那可以视为前一个关键点水平线的延申,不入结果集
//否则加入结果集
if (res.isEmpty()||cur_h!=res.get(res.size()-1).get(1)) {
List<Integer> ans=new ArrayList<>();
ans.add(cur_x);
ans.add(cur_h);
res.add(ans);
}
}
return res;
}
}
作者:tao-hua-dao-xiao-jian-ke
链接:https://leetcode-cn.com/problems/the-skyline-problem/solution/ji-hu-ji-bai-bai-fen-zhi-bai-de-jian-ji-ibr4z/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
双端队列
239.滑动窗口最大值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(k == 0 || nums.length == 0){
return new int[]{};
}
// 队列表示滑动窗口
//queue里面存储的是下标,这个下标对应的元素是单调递减的
Deque<Integer> queue = new LinkedList<Integer>();
//结果
int resIndex = 0;
int [] res = new int[nums.length - k + 1];
for(int i = 0; i < nums.length ; i ++){
// 头尾尾头口诀
// 1. 头:清理超期元素(清理i-k位置的元素)
if(!queue.isEmpty() && queue.peek() == i - k ){
queue.remove();
}
// 2. 尾:维护单调递减队列(清除队列内<新入队元素的元素)
//删除所有比新入队元素小的旧元素
while(!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]){
queue.removeLast();
}
// 3. 尾:新元素入队
queue.add(i);
// 4. 头:获取滑动窗口最大值(返回头部元素,i>=k -1时)
if(i >= k -1){
res[resIndex++] = nums[queue.peek()];
}
}
return res;
}
}
作者:yu-shu-lin-feng-7
链接:https://leetcode-cn.com/problems/sliding-window-maximum/solution/hua-dong-chuang-kou-de-zui-da-zhi-bao-mu-9eci/
哈希表
1.两数之和
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
int a=0;
Map<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],i);
}
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target-nums[i])&&i!=map.get(target-nums[i])) {
res[0]=i;
res[1]=map.get(target-nums[i]);
}
}
return res;
}
}
128.最长连续系列
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> hash = new HashSet<Integer>();
for(int x : nums) hash.add(x); //放入hash表中
int res = 0;
for(int x : hash)
{
if(!hash.contains(x-1))
{
int y = x; //以当前数x向后枚举
while(hash.contains(y + 1)) y++;
res = Math.max(res, y - x + 1); //更新答案
}
}
return res;
}
}
作者:lin-shen-shi-jian-lu-k
链接:https://leetcode-cn.com/problems/longest-consecutive-sequence/solution/ha-xi-zui-qing-xi-yi-dong-de-jiang-jie-c-xpnr/
594.最长和谐子序列
class Solution {
public int findLHS(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0)+1);
}
int res = 0;
for (Integer num : map.keySet()) {
Integer count1 = map.get(num);
Integer count2 = map.get(num + 1);
if (count2 != null) {
res=Integer.max(res, count1 +count2);
}
}
return res;
}
}
class Solution {
public int findLHS(int[] nums) {
Arrays.sort(nums);
int ans = 0;
int start = 0, end = 0;
while (end < nums.length) {
while (nums[end] - nums[start] > 1) {
start++;
}
if (nums[end] - nums[start] == 1) {
ans = Math.max(ans, end - start + 1);
}
end++;
}
return ans;
}
}
149.直线上最多的点数
public class leetCode149 {
public int maxPoints(int[][] points) {
if (points.length <= 2) return points.length;
int ans = 0;
for (int i = 0; i < points.length; i++) {
for (int j = i + 1; j < points.length; j++) {
int x1 = points[i][0], y1 = points[i][1], x2 = points[j][0], y2 = points[j][1];
int count = 2;
for (int k = j + 1; k < points.length; k++) {
int x = points[k][0], y = points[k][1];
if ((y - y1) * (x2 - x1) == (y2 - y1) * (x - x1)) count++;
}
ans = Math.max(ans, count);
}
}
return ans;
}
}
697.数组的度
利用哈希表的键值对存储数组中每个数字出现的次数,第一次出现的下标,最后一次出现的下标
最后遍历哈希表,得到出现次数最大的数字以及最后一次出现的下标减去第一次出现下标的最小值
//int[]记录出现次数,0-出现次数,1-第一次出现的下标,2-最后一次出现的下标
import java.util.HashMap;
import java.util.Map;
public class leetCode697 {
public int findShortestSubArray(int[] nums) {
Map<Integer,int[]> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])){
map.get(nums[i])[0]++;
map.get(nums[i])[2]=i;
}else {
map.put(nums[i],new int[]{1,i,i});
}
}
int minLen=0;
int maxCount=0;
for (Map.Entry<Integer, int[]> entry : map.entrySet()) {
int []arr=entry.getValue();
int curLen=arr[2]-arr[1]+1;
if (arr[0]>maxCount){
maxCount=arr[0];
minLen=curLen;
}else if (arr[0]==maxCount){
if (minLen>curLen)
minLen=curLen;
}
}
return minLen;
}
}
前缀和与积分图
303.区域和检索
private final int[] nums;
public NumArray(int[] nums) {
for (int i = 1; i < nums.length; i++) {
nums[i] += nums[i - 1];
}
this.nums = nums;
}
public int sumRange(int i, int j) {
return i == 0 ? nums[j] : nums[j] - nums[i - 1];
}
304.二维区域和检索
class NumMatrix {
int[][] sum;
public NumMatrix(int[][] matrix) {
int n = matrix.length, m = n == 0 ? 0 : matrix[0].length;
// 与「一维前缀和」一样,前缀和数组下标从 1 开始,因此设定矩阵形状为 [n + 1][m + 1](模板部分)
sum = new int[n + 1][m + 1];
// 预处理除前缀和数组(模板部分)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
public int sumRegion(int x1, int y1, int x2, int y2) {
// 求某一段区域和 [i, j] 的模板是 sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];(模板部分)
// 但由于我们源数组下标从 0 开始,因此要在模板的基础上进行 + 1
x1++; y1++; x2++; y2++;
return sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1 - 1] + sum[x1 - 1][y1 - 1];
}
}
作者:AC_OIer
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/xia-ci-ru-he-zai-30-miao-nei-zuo-chu-lai-ptlo/
560.和为k 的子数组
class Solution {
public int subarraySum(int[] nums, int k) {
if (nums.length == 0) {
return 0;
}
HashMap<Integer,Integer> map = new HashMap<>();
//细节,这里需要预存前缀和为 0 的情况,会漏掉前几位就满足的情况
//例如输入[1,1,0],k = 2 如果没有这行代码,则会返回0,漏掉了1+1=2,和1+1+0=2的情况
//输入:[3,1,1,0] k = 2时则不会漏掉
//因为presum[3] - presum[0]表示前面 3 位的和,所以需要map.put(0,1),垫下底
map.put(0, 1);
int count = 0;
int presum = 0;
for (int x : nums) {
presum += x;
//当前前缀和已知,判断是否含有 presum - k的前缀和,那么我们就知道某一区间的和为 k 了。
if (map.containsKey(presum - k)) {
count += map.get(presum - k);//获取次数
}
//更新
map.put(presum,map.getOrDefault(presum,0) + 1);
}
return count;
}
}
class Solution {
public int subarraySum(int[] nums, int k) {
//前缀和数组
int[] presum = new int[nums.length+1];
for (int i = 0; i < nums.length; i++) {
//这里需要注意,我们的前缀和是presum[1]开始填充的
presum[i+1] = nums[i] + presum[i];
}
//统计个数
int count = 0;
for (int i = 0; i < nums.length; ++i) {
for (int j = i; j < nums.length; ++j) {
//注意偏移,因为我们的nums[2]到nums[4]等于presum[5]-presum[2]
//所以这样就可以得到nums[i,j]区间内的和
if (presum[j+1] - presum[i] == k) {
count++;
}
}
}
return count;
}
}
作者:chefyuan
链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/de-liao-yi-wen-jiang-qian-zhui-he-an-pai-yhyf/
链表
206.反转链表
class Solution {
public ListNode reverseList(ListNode head) {
// 判断当前链表是否为空 || 只有一个节点, 无需反转, 直接返回
if (head == null || head.next == null) return head;
// 定义一个辅助的指针(变量), 帮助我们遍历链表
ListNode cur = head;
ListNode pre = null; // 指向当前节点[cur]的下一个节点
// 遍历原来的链表
while(cur != null){
ListNode temp = cur.next; // 先暂时保存当前节点的下一个节点, 后面会使用
cur.next = pre; // 将cur的下一个节点指向新的链表的最前端
pre = cur; //将cur连接到新的节点上
cur = temp; // 让cur后移
}
return pre;
}
}
234.回文链表
先使用快慢指针找到链表中点,再把链表切成两半;然后把前半段翻转;最后比较两半是否 相等。
public class leetCode234 {
public boolean isPalindrome(ListNode head) {
// 单节点也默认为回文
if (head == null || head.next == null)
return true;
ListNode slow = head, fast = head;
// pre为slow的上一节点
ListNode pre = null;
// 快慢指针同时反转前半部分链表(破坏了原来链表结构)
while (fast != null && fast.next != null) {
fast = fast.next.next;
// temp保存slow的下一节点,以免链表断裂
ListNode temp = slow.next;
// 反转链表
slow.next = pre;
// pre后移
pre = slow;
// slow后移
slow = temp;
}
// 应对奇数情况[1,0,1]:当fast.next为null时,fast必为末尾节点
if (fast != null)
slow = slow.next;
// prepre用于恢复原来链表结构
//ListNode prepre = slow;
// 判断是否为回文
while (pre != null && slow != null) {
if (pre.val != slow.val)
return false;
slow = slow.next;
pre=pre.next;
// 不还原链表时,直接pre = pre.next;
// 还原链表,上述while破坏了原有链表结构,建议最好复原
// temp保存pre的下一节点,以免反转后的链表断裂
// ListNode temp = pre.next;
// // 恢复原来的.next
// pre.next = prepre;
// // prepre向前移
// prepre = pre;
// // pre前移
// pre = temp;
}
return true;
}
}
83.删除链表中重复的元素
public class leetCode83 {
public ListNode deleteDuplicates(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next=head;
ListNode p=dummy.next;
while (p!=null&&p.next!=null){
if (p.val==p.next.val)
p.next=p.next.next;
else
p=p.next;
}
return dummy.next;
}
}
19.删除链表中倒数第N个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
//新建哑节点,方便处理头节点
ListNode dummy = new ListNode();
dummy.next = head;
ListNode fast = dummy, slow = dummy;
for (int i = 0; i < n; i++){
fast = fast.next;
}
while (fast.next != null){
fast = fast.next;
slow = slow.next;
}
//删除slow.next
slow.next = slow.next.next;
return dummy.next;
}
}
作者:Blizzard0409
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/19-shan-chu-lian-biao-de-dao-shu-di-n-ge-rrmc/
24.两两交换链表中的节点
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
for(ListNode p = dummy; p.next != null && p.next.next != null;)
{
ListNode a = p.next; //虚拟头节点
ListNode b = a.next;
p.next = b;
a.next = b.next;
b.next = a;
p = a;
}
return dummy.next;
}
}
作者:lin-shen-shi-jian-lu-k
链接:https://leetcode-cn.com/problems/swap-nodes-in-pairs/solution/24-liang-liang-jiao-huan-lian-biao-zhong-2kiy/
160.相交的链表
public class leetCode160 {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA,p2=headB;
while (p1!=p2){
p1=p1!=null?p1.next:headB;
p2=p2!=null?p2.next:headA;
}
return p1;
}
}
328.奇偶链表
class Solution {
public ListNode oddEvenList(ListNode head) {
// 分别定义奇偶链表的 虚拟头结点 和 尾结点
ListNode oddHead = new ListNode();
ListNode oddTail = oddHead;
ListNode evenHead = new ListNode();
ListNode evenTail = evenHead;
// 遍历原链表,根据 isOdd 标识位决定将当前结点插入到奇链表还是偶链表(尾插法)
boolean isOdd = true;
while (head != null) {
if (isOdd) {
oddTail.next = head;
oddTail = oddTail.next;
} else {
evenTail.next = head;
evenTail = evenTail.next;
}
head = head.next;
isOdd = !isOdd;
}
// 将奇链表后面拼接上偶链表,并将偶链表的next设置为null
oddTail.next = evenHead.next;
evenTail.next = null;
return oddHead.next;
}
}
作者:sweetiee
链接:https://leetcode-cn.com/problems/odd-even-linked-list/solution/kuai-lai-wu-nao-miao-dong-qi-ou-lian-biao-by-sweet/
148.排序链表
class Solution {
public ListNode sortList(ListNode head) {
//终止条件,没有节点或者一个节点就不需要排序了
if (head == null || head.next == null) return head;
//快慢指针找到链表中点
ListNode fast = head, slow = head;
while (fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
//从中间断开
ListNode rHead = slow.next;
slow.next = null;
//递归处理前后两个链表
ListNode left = sortList(head);
ListNode right = sortList(rHead);
//连个链表处理完,进行合并
return merge(left, right);
}
public ListNode merge(ListNode left, ListNode right){
if (left == null) return right;
if (right == null) return left;
//res为哑节点,方便处理链表。 resTail为res的尾指针,用来插入节点
ListNode res = new ListNode(), resTail = res;
//left和right哪个小就接入res
while (left != null && right != null){
if (left.val < right.val){
resTail.next = left;
left = left.next;
}else{
resTail.next = right;
right = right.next;
}
resTail = resTail.next;
}
//最后如果两个链表还有剩余
if (left != null) resTail.next = left;
if (right != null) resTail.next = right;
//返回结果
return res.next;
}
}
作者:Blizzard0409
链接:https://leetcode-cn.com/problems/sort-list/solution/di-gui-fang-fa-gui-bing-pai-xu-java-by-b-x4sf/
二叉树
二叉树的属性
104.二叉树的最大深度
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
这一点其实是很多同学没有想清楚的,很多题解同样没有讲清楚。
我先用后序遍历(左右中)来计算树的高度。
确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
代码如下:
int getdepth(treenode* node)
确定终止条件:如果为空节点的话,就返回0,表示高度为0。
代码如下:
if (node == null) return 0;
确定单层递归的逻辑:先求它的左子树的深度,再求的右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
代码如下:
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
作者:carlsun-2
链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/solution/dai-ma-sui-xiang-lu-qiu-shu-de-zui-da-sh-f988/
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;
}
}
public class leetCode104 {
public int maxDepth(TreeNode root) {
if(root==null) return 0;
int leftDepth=maxDepth(root.left);//左
int rightDepth=maxDepth(root.right);//右
int depth=Math.max(leftDepth,rightDepth)+1;//中
return depth;
}
/**
* 迭代法,使用层序遍历
*/
public int maxdepth2(TreeNode root) {
if (root==null) return 0;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
int depth=0;
while (!queue.isEmpty()){
int size=queue.size();
depth++;
for (int i=0;i<size;i++){
TreeNode poll=queue.poll();
if (poll.left!=null) queue.add(poll.left);
if (poll.right!=null) queue.add(poll.right);
}
}
return depth;
}
}
110.平衡二叉树
递归返回值:
当节点root 左 / 右子树的高度差 < 2<2 :则返回以节点root为根节点的子树的最大高度,即节点 root 的左右子树中最大高度加 11 ( max(left, right) + 1 );
当节点root 左 / 右子树的高度差 \geq 2≥2 :则返回 -1−1 ,代表 此子树不是平衡树 。
递归终止条件:
当越过叶子节点时,返回高度 00 ;
当左(右)子树高度 left== -1 时,代表此子树的 左(右)子树 不是平衡树,因此直接返回 -1−1 ;
public class leetCode110 {
public boolean isBalanced(TreeNode root) {
return getDepth(root)!=-1;
}
private int getDepth(TreeNode root){
if (root==null) return 0;
int leftDepth=getDepth(root.left);
if (leftDepth==-1) return -1;
int rightDepth=getDepth(root.right);
if (rightDepth==-1) return -1;
int depth=Math.max(leftDepth,rightDepth)+1;
return Math.abs(leftDepth-rightDepth)<2?depth:-1;
}
}
513,最左小角的值
思路一:BFS
用队列存储节点,先进先出
从右往左遍历,也就是在往队列中添加数据时,先添加右子节点,再添加左子节点
当队列为空时,循环结束,最后一个遍历到的节点就是最左边的节点
返回最左边节点的值
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode node = null;
while(!queue.isEmpty()){
node = queue.poll();
// 先右后左
if(node.right != null){
queue.offer(node.right);
}
if(node.left != null){
queue.offer(node.left);
}
}
return node.val;
}
}
作者:edelweisskoko
链接:https://leetcode-cn.com/problems/find-bottom-left-tree-value/solution/513-zhao-shu-zuo-xia-jiao-de-zhi-bfs-dfs-aawe/
思路二:DFS
只有叶子节点可能是需要的返回值res,所以递归的中止条件为当遇到叶子节点才return
只有当前叶子节点的深度比之前的更大,才更新res
PS:因为要大于才会更新,所以每一层只会更新一次,先会遍历到左边的节点,所以只会更新每层最左侧的节点,符合题目要求
因为要比较深度,在递归中除了节点作为参数外,还需要增加深度参数
在res中增加一项专门用于存储深度, 即 res = [叶子节点值,叶子节点深度]
最后返回的是res[0]
class Solution {
private int[] res;
public int findBottomLeftValue(TreeNode root) {
res = new int[]{0, -1};
dfs(root, 0);
return res[0];
}
private void dfs(TreeNode node, int level){
if(node.left == null && node.right == null){
if(level > res[1]){
res[0] = node.val;
res[1] = level;
}
return;
}
if(node.left != null){
dfs(node.left, level + 1);
}
if(node.right != null){
dfs(node.right, level + 1);
}
}
}
404.左子树的值
public class leetCode404 {
int res=0;
public int sumOfLeftLeaves(TreeNode root) {
if (root==null) return 0;
helper(root);
return res;
}
public void helper(TreeNode root) {
if (root==null) return;
if (root.left!=null&& root.left.left==null&&root.left.right==null)
res+=root.left.val;
helper(root.left);
helper(root.right);
}
}
235.二叉搜索树的最近公共祖先节点
如果两个节点值都小于根节点,说明他们都在根节点的左子树上,我们往左子树上找
如果两个节点值都大于根节点,说明他们都在根节点的右子树上,我们往右子树上找
如果一个节点值大于根节点,一个节点值小于根节点,说明他们他们一个在根节点的左子树上一个在根节点的右子树上,那么根节点就是他们的最近公共祖先节点。
public TreeNode lowestCommonAncestor(TreeNode cur, TreeNode p, TreeNode q) {
if (cur == null || cur == p || cur == q)
return cur;
TreeNode left = lowestCommonAncestor(cur.left, p, q);
TreeNode right = lowestCommonAncestor(cur.right, p, q);
//如果left为空,说明这两个节点在cur结点的右子树上,我们只需要返回右子树查找的结果即可
if (left == null)
return right;
//同上
if (right == null)
return left;
//如果left和right都不为空,说明这两个节点一个在cur的左子树上一个在cur的右子树上,
//我们只需要返回cur结点即可。
return cur;
}
作者:sdwwld
链接:https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/solution/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-3c/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
236.二叉树的最近公共祖先节点
public class leetCode236 {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==null||p==root||q==root) return root;
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
if (left==null) return right;
if (right==null) return left;
return root;
}
}
530.二叉搜索树的最小绝对值差
遇到在二叉搜索树上求什么最值,求差值之类的,都要思考一下二叉搜索树可是有序(中序遍历)的,要利用好这一特点。
要同时记录前后两个指针。
class Solution {
int res = Integer.MAX_VALUE;
TreeNode pre = null;
public int getMinimumDifference(TreeNode root) {
dfs(root);
return res;
}
private void dfs(TreeNode root){
if(root == null) return;
dfs(root.left);
if(pre != null ){
res = Math.min(res,root.val - pre.val);
}
pre = root;
dfs(root.right);
}
}
作者:linniu
链接:https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/solution/lindi-gui-guo-cheng-ji-lu-shang-yi-ge-ji-pava/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
653.两数之和
import java.util.HashSet;
import java.util.Set;
public class leetCode653 {
public boolean findTarget(TreeNode root, int k) {
if (root==null) return false;
Set<Integer> set=new HashSet<>();
return find(root,k,set);
}
private boolean find(TreeNode root, int k, Set<Integer> set){
if (root==null) return false;
if (set.contains(k-root.val)) return true;
set.add(root.val);
return find(root.left,k,set)||find(root.right,k,set);
}
}
二叉树的遍历
637.二叉树的层平均值
public class leetCode637 {
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res=new ArrayList<>();
if (root==null) return res;
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
int size=queue.size();
double leverSum=0;
for (int i = 0; i < size; i++) {
TreeNode poll=queue.poll();
leverSum+=poll.val;
if (poll.left!=null){
queue.add(poll.left);
}
if (poll.right!=null){
queue.add(poll.right);
}
}
res.add(leverSum/size);
}
return res;
}
}
144.二叉树的前序遍历
https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/leetcodesuan-fa-xiu-lian-dong-hua-yan-shi-xbian-2/
public class leetCode144 {
List<Integer> res=new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
pre(root);
return res;
}
private void pre(TreeNode root){
if (root==null) return;
res.add(root.val);
pre(root.left);
pre(root.right);
}
public List<Integer> preorderTraversal2(TreeNode root) {
if (root==null) return res;
Stack<TreeNode> stack=new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode node=stack.pop();
res.add(node.val);
if (node.right!=null) stack.push(node.right);
if (node.left!=null) stack.push(node.left);
}
return res;
}
}
94.二叉树的中序遍历
public class leetCode95 {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if (root == null) return res;
dfs(root);
return res;
}
public void dfs(TreeNode root) {
if (root == null) return;
dfs(root.left);
res.add(root.val);
dfs(root.right);
}
}
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
while(stack.size()>0 || root!=null) {
//不断往左子树方向走,每走一次就将当前节点保存到栈中
//这是模拟递归的调用
if(root!=null) {
stack.add(root);
root = root.left;
//当前节点为空,说明左边走到头了,从栈中弹出节点并保存
//然后转向右边节点,继续上面整个过程
} else {
TreeNode tmp = stack.pop();
res.add(tmp.val);
root = tmp.right;
}
}
return res;
}
}
145.二叉树的后序遍历
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root == null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode curNode = stack.pop();
res.add(curNode.val);
if(curNode.left != null){
stack.push(curNode.left);
}
if(curNode.right != null){
stack.push(curNode.right);
}
}
Collections.reverse(res);
return res;
}
}
作者:linniu
链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal/solution/lin-di-gui-die-dai-by-linniu-kgl4/
617.合并二叉树
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null || t2==null) {
return t1==null? t2 : t1;
}
return dfs(t1,t2);
}
TreeNode dfs(TreeNode r1, TreeNode r2) {
// 如果 r1和r2中,只要有一个是null,函数就直接返回
if(r1==null || r2==null) {
return r1==null? r2 : r1;
}
//让r1的值 等于 r1和r2的值累加,再递归的计算两颗树的左节点、右节点
r1.val += r2.val;
r1.left = dfs(r1.left,r2.left);
r1.right = dfs(r1.right,r2.right);
return r1;
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/merge-two-binary-trees/solution/dong-hua-yan-shi-di-gui-die-dai-617he-bing-er-cha-/
二叉树结构或修改
226.交换二叉树
class Solution {
public TreeNode invertTree(TreeNode root) {
//递归函数的终止条件,节点为空时返回
if(root==null) {
return null;
}
//下面三句是将当前节点的左右子树交换
TreeNode tmp = root.right;
root.right = root.left;
root.left = tmp;
//递归交换当前节点的 左子树
invertTree(root.left);
//递归交换当前节点的 右子树
invertTree(root.right);
//函数返回时就表示当前这个节点,以及它的左右子树
//都已经交换完了
return root;
}
}
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null) {
return null;
}
//将二叉树中的节点逐层放入队列中,再迭代处理队列中的元素
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()) {
//每次都从队列中拿一个节点,并交换这个节点的左右子树
TreeNode tmp = queue.poll();
TreeNode left = tmp.left;
tmp.left = tmp.right;
tmp.right = left;
//如果当前节点的左子树不为空,则放入队列等待后续处理
if(tmp.left!=null) {
queue.add(tmp.left);
}
//如果当前节点的右子树不为空,则放入队列等待后续处理
if(tmp.right!=null) {
queue.add(tmp.right);
}
}
//返回处理完的根节点
return root;
}
}
101.对称二叉树
乍一看无从下手,但用递归其实很好解决。
根据题目的描述,镜像对称,就是左右两边相等,也就是左子树和右子树是相当的。
注意这句话,左子树和右子相等,也就是说要递归的比较左子树和右子树。
我们将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。
如果相当,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点
比如看下面这两个子树(他们分别是根节点的左子树和右子树),能观察到这么一个规律:
左子树 22 的左孩子 == 右子树 22 的右孩子
左子树 22 的右孩子 == 右子树 22 的左孩子
根据上面信息可以总结出递归函数的两个条件:
终止条件:
left 和 right 不等,或者 left 和 right 都为空
递归的比较 left,left 和 right.right,递归比较 left,right 和 right.left
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) {
return true;
}
//调用递归函数,比较左节点,右节点
return dfs(root.left,root.right);
}
boolean dfs(TreeNode left, TreeNode right) {
//递归的终止条件是两个节点都为空
//或者两个节点中有一个为空
//或者两个节点的值不相等
if(left==null && right==null) {
return true;
}
if(left==null || right==null) {
return false;
}
if(left.val!=right.val) {
return false;
}
//再递归的比较 左节点的左孩子 和 右节点的右孩子
//以及比较 左节点的右孩子 和 右节点的左孩子
return dfs(left.left,right.right) && dfs(left.right,right.left);
}
}
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null || (root.left==null && root.right==null)) {
return true;
}
//用队列保存节点
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
//将根节点的左右孩子放到队列中
queue.add(root.left);
queue.add(root.right);
while(queue.size()>0) {
//从队列中取出两个节点,再比较这两个节点
TreeNode left = queue.removeFirst();
TreeNode right = queue.removeFirst();
//如果两个节点都为空就继续循环,两者有一个为空就返回false
if(left==null && right==null) {
continue;
}
if(left==null || right==null) {
return false;
}
if(left.val!=right.val) {
return false;
}
//将左节点的左孩子, 右节点的右孩子放入队列
queue.add(left.left);
queue.add(right.right);
//将左节点的右孩子,右节点的左孩子放入队列
queue.add(left.right);
queue.add(right.left);
}
return true;
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/symmetric-tree/solution/dong-hua-yan-shi-101-dui-cheng-er-cha-shu-by-user7/
105.从前序和后序遍历构造二叉树
public TreeNode buildTree(int[] preorder, int[] inorder) {
return buildTreeHelper(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
private TreeNode buildTreeHelper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
// preorder 为空,直接返回 null
if (p_start == p_end) {
return null;
}
int root_val = preorder[p_start];
TreeNode root = new TreeNode(root_val);
//在中序遍历中找到根节点的位置
int i_root_index = 0;
for (int i = i_start; i < i_end; i++) {
if (root_val == inorder[i]) {
i_root_index = i;
break;
}
}
int leftNum = i_root_index - i_start;
//递归的构造左子树
root.left = buildTreeHelper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
//递归的构造右子树
root.right = buildTreeHelper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
return root;
}
作者:windliang
链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--22/
106.从中序和后序遍历构造二叉树
利用递归的思想,每一次都往递归函数中传入本次分割的inorder数组,inLeft左值,inRight右值和postorder数组,postLeft左值,postRight右值。注意在递归是要明确边界的取舍情况,以免递归时出现错乱。本解法中给出的取舍情况都是左闭右开,取左不取右,也可以采用其他的取舍情况。
每一次递归后序数组的postRight-1处的值,就是下一次分割的根节点的值。所以split.right = buildTreeHelper(inorder,postorder,postIndex+1,inRight,postLeft+postIndex-inLeft,postRight-1);这里最后要有一个-1的操作。
每次在后序数组中找到根节点之后,中序数组中寻找切割点。
public class leetCode106 {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return helperbuildTree(inorder,0,inorder.length,postorder,0,postorder.length);
}
public TreeNode helperbuildTree(int[] inorder, int i_start,int i_end,
int[] postorder,int p_start,int p_end) {
if (i_start==i_end||p_end==p_start) return null;
int root_val=postorder[p_end-1];
TreeNode root = new TreeNode(root_val);
int i_root_index=0;
for (int i = i_start; i < i_end; i++) {
if (root_val==inorder[i]) i_root_index=i;
}
int left_len=i_root_index-i_start;
root.left=helperbuildTree(inorder,i_start,i_root_index,postorder,p_start,p_start+left_len);
root.right=helperbuildTree(inorder,i_root_index+1,i_end,postorder,p_start+left_len,p_end-1);
return root;
}
889.从前序和后序遍历构造二叉树
class Solution {
public TreeNode constructFromPrePost(int[] pre, int[] post) {
if(pre==null || pre.length==0) {
return null;
}
return dfs(pre,post);
}
private TreeNode dfs(int[] pre,int[] post) {
if(pre==null || pre.length==0) {
return null;
}
//数组长度为1时,直接返回即可
if(pre.length==1) {
return new TreeNode(pre[0]);
}
//根据前序数组的第一个元素,创建根节点
TreeNode root = new TreeNode(pre[0]);
int n = pre.length;
for(int i=0;i<post.length;++i) {
if(pre[1]==post[i]) {
//根据前序数组第二个元素,确定后序数组左子树范围
int left_count = i+1;
//拆分前序和后序数组,分成四份
int[] pre_left = Arrays.copyOfRange(pre,1,left_count+1);
int[] pre_right = Arrays.copyOfRange(pre,left_count+1,n);
int[] post_left = Arrays.copyOfRange(post,0,left_count);
int[] post_right = Arrays.copyOfRange(post,left_count,n-1);
//递归执行前序数组左边、后序数组左边
root.left = dfs(pre_left,post_left);
//递归执行前序数组右边、后序数组右边
root.right = dfs(pre_right,post_right);
break;
}
}
//返回根节点
return root;
}
}
作者:wang_ni_ma
链接:https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/solution/tu-jie-889-gen-ju-qian-xu-he-hou-xu-bian-li-gou-2/
669.修剪二叉搜索树
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return null;
// 如果root会被删除 则选择性返回左右! 包括根节点 每个节点都这么处理就好了
if(root.val > high) return trimBST(root.left, low, high);
if(root.val < low) return trimBST(root.right, low, high);
// 如果root本身没事 那就还是左右都向下递归找
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
}
}
572.另一棵树的子树
④
/ \
① ⑥
右
dfs(root.right); //就是拿到6的值,此时sum = 6,root.val = 6
中
sum += root.val;
root.val = sum;
这里就是拿到 6 + 4的值,此时sum = 10,root.val = 10
左
dfs(root.left);
这里就是拿到6+4+1的值,此时sum = 11,root.val = 11
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
//递归结束条件1:当二者都为空时,返回true
if(s==null && t==null) return true;
//递归结束条件2:当有一方已经遍历完,但另一颗还未遍历完时,返回false
if(s==null || t==null) return false;
/*t为s的子树,共有三种情况:
1. t和s相同;
2. t是s的某个左子树;
3. t是s的某个右子树
只要符合三条中的一条,就是符合题意的*/
return helper(s,t) || isSubtree(s.left,t) || isSubtree(s.right,t);
}
//判断两颗树是否相同
public boolean helper(TreeNode s,TreeNode t){
//递归结束条件1:当二者都为空时,返回true
if(s==null && t==null) return true;
//递归结束条件2:当有一方已经遍历完,但另一颗还未遍历完时,返回false
if(s==null || t==null) return false;
//当二者的值不相同,肯定是false
if(s.val!=t.val) return false;
//继续遍历s和t的左右子树,看是否都完全相同
return helper(s.left,t.left) && helper(s.right,t.right);
}
}
作者:Destinytomycode
链接:https://leetcode-cn.com/problems/subtree-of-another-tree/solution/javaji-jian-zhu-yi-by-destinytomycode-sbdl/
538.把二叉搜索树变成累加树
实际上:从下到上,从左到右的顺序累加:首先要明确累加树的遍历顺序是右中左。
class Solution {
int sum;
public TreeNode convertBST(TreeNode root) {
sum = 0;
dfs(root);
return root;
}
private void dfs(TreeNode root){
if(root == null) return;
//右
dfs(root.right);
//中
sum += root.val;
root.val = sum;
//左
dfs(root.left);
}
}
作者:linniu
链接:https://leetcode-cn.com/problems/convert-bst-to-greater-tree/solution/lindi-gui-by-linniu-072j/
109.把有序链表变成二叉搜索树
public TreeNode sortedListToBST(ListNode head) {
//边界条件的判断
if (head == null)
return null;
if (head.next == null)
return new TreeNode(head.val);
//这里通过快慢指针找到链表的中间结点slow,pre就是中间
//结点slow的前一个结点
ListNode slow = head, fast = head, pre = null;
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
//链表断开为两部分,一部分是node的左子节点,一部分是node
//的右子节点
pre.next = null;
//node就是当前节点
TreeNode node = new TreeNode(slow.val);
//从head节点到pre节点是node左子树的节点
node.left = sortedListToBST(head);
//从slow.next到链表的末尾是node的右子树的结点
node.right = sortedListToBST(slow.next);
return node;
}
作者:sdwwld
链接:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/solution/kuai-man-zhi-zhen-jie-jue-ji-bai-liao-100de-yong-h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
897.递增顺序搜索树
//第一种方法
class Solution {
ArrayList<Integer> list=new ArrayList<>();
public TreeNode increasingBST(TreeNode root) {
dfs(root);
TreeNode t=composeTreeNode(list);
return t;
}
public void dfs(TreeNode root) {
if(root != null){
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
}
public TreeNode composeTreeNode(ArrayList<Integer> list) {
TreeNode i=new TreeNode(0),cur=i;
for(int j:list){
cur.right=new TreeNode(j);
cur=cur.right;
}
return i.right;
}
}
//第二种方法
class Solution {
TreeNode l=new TreeNode(0);
TreeNode cur=l;
public TreeNode increasingBST(TreeNode root) {
dfs(root);
return cur.right;
}
public void dfs(TreeNode root) {
if(root != null){
dfs(root.left);
TreeNode tmp = new TreeNode(root.val);
l.right=tmp;
l=tmp;
dfs(root.right);
}
}
}
作者:Qiumg
链接:https://leetcode-cn.com/problems/increasing-order-search-tree/solution/java-zhi-jie-cun-zai-listli-mian-ran-hou-usn1/
450.删除二叉搜索树的节点
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null){
return null;
}
//当前节点值比key小,则需要删除当前节点的左子树中key对应的值,并保证二叉搜索树的性质不变
if(key < root.val){
root.left = deleteNode(root.left,key);
}
//当前节点值比key大,则需要删除当前节点的右子树中key对应的值,并保证二叉搜索树的性质不变
else if(key > root.val){
root.right = deleteNode(root.right,key);
}
//当前节点等于key,则需要删除当前节点,并保证二叉搜索树的性质不变
else{
//当前节点没有左子树
if(root.left == null){
return root.right;
}
//当前节点没有右子树
else if(root.right == null){
return root.left;
}
//当前节点既有左子树又有右子树
else{
TreeNode node = root.right;
//找到当前节点右子树最左边的叶子结点
while(node.left != null){
node = node.left;
}
//将root的左子树放到root的右子树的最下面的左叶子节点的左子树上
node.left = root.left;
return root.right;
}
}
return root;
}
}
作者:Geralt_U
链接:https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/450-shan-chu-er-cha-sou-suo-shu-zhong-de-jie-dia-6/
路径问题
一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析):
https://leetcode-cn.com/problems/diameter-of-binary-tree/solution/yi-pian-wen-zhang-jie-jue-suo-you-er-cha-6g00/
自顶向下
437.路径总和三
public class leetCode437 {
int cnt=0;
public int pathSum(TreeNode root, int targetSum) {
if (root==null) return 0;
dfs(root,targetSum);
pathSum(root.left,targetSum);
pathSum(root.right,targetSum);
return cnt;
}
private void dfs(TreeNode root,int targerSum){
if (root==null) return ;
targerSum-=root.val;
if (targerSum==0) cnt++;
dfs(root.left,targerSum);
dfs(root.right,targerSum);
}
}
1110.删点成林
List<TreeNode> res=new ArrayList<>();
public List<TreeNode> delNodes(TreeNode root, int[] to_delete) {
Set<Integer> hashset = new HashSet<>();
for (int i : to_delete) hashset.add(i);
if (!hashset.contains(root.val)) res.add(root);
DFS(root,hashset);
return res;
}
public TreeNode DFS(TreeNode root,Set<Integer> hashset) {
if (root == null) return null;
root.left = DFS(root.left,hashset);
root.right = DFS(root.right,hashset);
if (hashset.contains(root.val)) {
if (root.left != null) res.add(root.left);
if (root.right != null) res.add(root.right);
root = null;
}
return root;
}
作者:CYINGENOHALT
链接:https://leetcode-cn.com/problems/delete-nodes-and-return-forest/solution/javahou-xu-bian-li-100-by-cyingenohalt-tout/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
非自顶向下
543.二叉树的直径
直径,不一定经过根节点!
每遇到一个节点,都求一下左子树的最大深度left,和右子树的最大深度right。此时经过当前节点的最大直径,就是left + right,与res比较更新即可。
class Solution {
int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
getDepth(root);
return res;
}
public int getDepth(TreeNode root){
if (root == null) return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
res = Math.max(res, left + right);
return Math.max(left, right) + 1;
}
}
前缀树
208.实现前缀树
// https://leetcode-cn.com/problems/implement-trie-prefix-tree/solution/shu-ju-jie-gou-he-suan-fa-zi-dian-shu-de-6t43/
class TrieNode {
boolean isWord;//是否是单词
TrieNode[] children;//26个小写字母
public TrieNode() {
isWord = false;
children = new TrieNode[26];
}
}
public class Trie{
//根节点,根节点是不存储任何字母的,从根节点的
//子节点开始存储
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node=root;
for (char c : word.toCharArray()) {
if (node.children[c-'a']==null) node.children[c-'a']=new TrieNode();
node=node.children[c-'a'];
}
node.isWord=true;
}
public boolean search(String word) {
TrieNode node=root;
for (char c : word.toCharArray()) {
node=node.children[c-'a'];
if (node==null) return false;
}
return node.isWord;
}
public boolean startsWith(String prefix) {
TrieNode node=root;
for (char c : prefix.toCharArray()) {
node=node.children[c-'a'];
if (node==null) return false;
}
return true;
}
}
java面试基础
1.java中创建对象的几种方式
-
使用new 关键字
使用 new 关键字创建对象,实际上是做了两个工作,一是在内存中开辟空间,二是初始化对象。但是new 关键字只能创建非抽象对象。 -
使用反射创建对象
反射是对于任意一个正在运行的类,都能动态获取到他的属性和方法。反射创建对象分为两种方式,一是使用Class类的new Instance() 方法,二是使用Constructor类的new Instatnce() 方法。
两者区别在于:
Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。
-
使用clone方法
要拷贝的对象需要实现Cloneable类,并重写clone()方法。 -
使用反序列化方式
当序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。为了反序列化一个对象,需要让类实现Serializable接口。然后在使用new ObjectInputStream().readObject() 来创建对象。
text
public class Order implements Cloneable, Serializable{
private String code;
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return (Order)super.clone();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Order order = (Order) o;
if (code != null ? !code.equals(order.code) : order.code != null) return false;
return name != null ? name.equals(order.name) : order.name == null;
}
@Override
public int hashCode() {
int result = code != null ? code.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws Exception {
// 1、使用new关键值创建对象
Order order1 = new Order();
order1.setCode("111");
order1.setName("订单1");
System.out.println(order1);
// 2、使用clone
Order order2 = new Order();
Object clone = order2.clone();
System.out.println(clone);
// 3、使用反射
Class c = Class.forName("cn.qidd.other.Order");
Object o = c.newInstance();
System.out.println(o);
// 4、反序列化
// 先序列化
Order order3 = new Order();
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("order.obj"));
os.writeObject(order3);
// 再反序列化
ObjectInputStream is = new ObjectInputStream(new FileInputStream("order.obj"));
Object o1 = is.readObject();
System.out.println(o1);
}