【LeectCode】刷题指南

LeetCode刷题指南

刷题流程参照link

算法

双指针

633平方数之和

限制开始结束范围(升序) 根据结果值微调

public boolean judgeSquareSum(int c) {
        int start = 0,end=(int)Math.sqrt(c);
        while(start<=end){
            int sum = start*start+end*end;
            if(sum==c){
                return true;
            }else if(sum>c){
                end--;
            }else{
                start++;
            }
        }
        return false;
    }

345反转字符串中的元音字母_

走两边向中间开始找

private static final HashSet<Character> vowels = new HashSet<>(Arrays.asList('a','e','i','o','u','A','E','I','O','U'));

    public String reverseVowels(String s) {
        if(s==null)return null;
        int startPtr = 0,endPtr = s.length()-1;

        char[] chars = new char[s.length()];
        while(startPtr<=endPtr){
            char sChar = s.charAt(startPtr);
            char eChar = s.charAt(endPtr);
            if(!vowels.contains(sChar)){
                chars[startPtr++] = sChar;
            }else if(!vowels.contains(eChar)){
                chars[endPtr--] = eChar;
            }else{
                chars[startPtr++] = eChar;
                chars[endPtr--] = sChar;
            }
        }
        return new String(chars);
    }

680验证回文字符串_

最多允许删一个字符 当首次出现不相等时导致两种可能

public boolean validPalindrome(String s) {
        for(int i=0,j=s.length()-1;i<j;i++,j--){
            if(s.charAt(i)!=s.charAt(j)){
                return isPalidrome(s,i+1,j)||isPalidrome(s,i,j-1);
            }   
        }
        return true;
    }
    private boolean isPalidrome(String s,int startPtr,int endPtr){
        while(startPtr<endPtr){
            if(s.charAt(startPtr++)!=s.charAt(endPtr--)){
                return false;
            }
        }
        return true;
    }

141环形链表

有环的话,快的肯定能追上慢的

public boolean hasCycle(ListNode head) {
        if(head==null)return false;
        ListNode slow = head,fast=head.next;
        while(slow!=null&&fast!=null&&fast.next!=null){
            if(slow==fast)return true;
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;
    }

524通过删除字母匹配到字典里最长单词

public String findLongestWord(String s, List<String> dictionary) {
        String max_str = "";
        for(String str:dictionary){
            if(isSubSequence(str,s)){
                if(str.length()>max_str.length()||(str.length()==max_str.length()&&str.compareTo(max_str)<0)){
                    max_str = str;
                }
            }
        }
        return max_str;
    }

//判断s是否按照str字符出现的顺序包含
    private boolean isSubSequence(String str,String s){
        int j = 0;
        for(int i =0;j<str.length()&&i<s.length();i++){
            if(str.charAt(j)==s.charAt(i)){
                j++;
            }
        }
        return j == str.length();
    }

贪心思想

435.无重叠区间

public int eraseOverlapIntervals(int[][] intervals) {
        if(intervals.length==0)return 0;
        Arrays.sort(intervals,Comparator.comparingInt(o->o[1]));

        int end = intervals[0][1];
        int cnt = 1; //第一个元素 end是最小的,之前排过序

        for(int[]item:intervals){
            if(item[0]>=end){ //起始比前一个end大,说明没有交叉
                end = item[1];
                cnt++;
            }
        }
        return intervals.length-cnt;
    }

使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:

Arrays.sort(intervals, new Comparator<int[]>() {
     @Override
     public int compare(int[] o1, int[] o2) {
         return (o1[1] < o2[1]) ? -1 : ((o1[1] == o2[1]) ? 0 : 1);
     }
});

和扎气球的想法是一样的,找非重叠区域,边界相同的为重叠
452.用最少数量的箭引爆气球

646.最长数对链

56. 合并区间

和扎气球问题挺像的


public int[][] merge(int[][] intervals) {
        if(intervals.length==0){
            return new int[0][2];
        }
        Arrays.sort(intervals,(a,b)->a[0]-b[0]);

        List<int[]> list = new ArrayList();
        for(int i=0;i<intervals.length;i++){
            int l = intervals[i][0];
            int r = intervals[i][1];
            if(i==0||list.get(list.size()-1)[1]<l){
                list.add(intervals[i]);
            }else{
                list.get(list.size()-1)[1] = Math.max(r,list.get(list.size()-1)[1]);
            }
        }
        return list.toArray(new int[list.size()][]);
    }

406. 根据身高重建队列

按照身高降序 相同则按照k升序 前面的身高都比当前高,然后就是依据k调整位置

public int[][] reconstructQueue(int[][] people) {
        if(people.length==0||people[0].length==0)return null;
        //按照身高降序 相同则按照k升序
        Arrays.sort(people,(p1,p2)->p1[0]==p2[0]?p1[1]-p2[1]:p2[0]-p1[0]);

        List<int[]> list = new ArrayList<>();
        for(int[]p:people){
            list.add(p[1],p);  //依据k调整位置
        }
        return list.toArray(new int[list.size()][]);
    }

122. 买卖股票的最佳时机 II

可多次买卖(再次买之前得卖掉),有利可图就给加上

public int maxProfit(int[] prices) {
        int profit = 0;
        for(int i=0;i<prices.length-1;i++){
            if(prices[i+1]-prices[i]>0){
                profit+=prices[i+1]-prices[i];
            }
        }
        return profit;
    }

605. 种花问题

间隔种花

public boolean canPlaceFlowers(int[] flowerbed, int n) {
        int cnt = 0;
        int len = flowerbed.length;
        for(int i=0;i<len&&cnt<n;i++){
            if(flowerbed[i]==1)continue;
            int pre = i==0?0:flowerbed[i-1]; //首尾特殊处理
            int next = i==len-1?0:flowerbed[i+1];
            if(pre==0&&next==0){
                cnt++;
                flowerbed[i] = 1; //种花
            }
        }
        return cnt>=n;
    }

763. 划分字母区间

public List<Integer> partitionLabels(String S) {
        int[] lastLocation = new int[26]; //记录字符最后出现的位置索引
        for(int i=0;i<S.length();i++){
            lastLocation[charToindex(S.charAt(i))] = i;
        }
        int firstIndex = 0;
        List<Integer> partitions = new ArrayList<>();
        while(firstIndex<S.length()){
            int lastIndex = firstIndex;
            for(int i=firstIndex;i<S.length()&&i<=lastIndex;i++){
                int index = lastLocation[charToindex(S.charAt(i))]; //取出字符的最后出现位置
                if(index>lastIndex){
                    lastIndex=index;
                }
            }
            partitions.add(lastIndex-firstIndex+1);
            firstIndex = lastIndex+1;
        }
        return partitions;

    }
    private int charToindex(char c){
        return c-'a';
    }

分治

241.为运算表达式设计优先级

利用函数的意思进行同层面的逻辑组合:运算符 左边可能性 右边可能性 双层遍历添加到当前

public List<Integer> diffWaysToCompute(String expression) {
        List<Integer>ways = new ArrayList<Integer>();
        for(int i=0;i<expression.length();i++){
            char c = expression.charAt(i);
            if(c=='+'||c=='-'||c=='*'){
                List<Integer> left = diffWaysToCompute(expression.substring(0,i));
                List<Integer>right = diffWaysToCompute(expression.substring(i+1));
                for(int l:left){
                    for(int r:right){
                        switch(c){
                            case '+':ways.add(l+r);break;
                            case '-':ways.add(l-r);break;
                            case '*':ways.add(l*r);break;
                        }
                    }
                }
            }
        }
        if(ways.size()==0){ //即没有符号
            ways.add(Integer.valueOf(expression));
        }
        return ways;
    }

动态规划

斐波那契数列

70.爬楼梯

滚动三元数组的思想

    public int climbStairs(int n) {
        int pre2 = 0,pre1=1;
        for(int i=1;i<=n;i++){
			int cnt = pre2 + pre1; //当前台阶i的来源于i-1或者i-2即他们的类别数之和
            pre2 = pre1;
            pre1 = cnt;   
        }
        return pre1;
    }

198.打家劫舍

滚动的三元数组,类似爬楼梯

public int rob(int[] nums) {
        int pre2 = 0,pre1=0;
        for(int num:nums){
            int cur = Math.max(pre2+num,pre1); //获取当前节点处可get的最大值
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
    }

213.打家劫舍 II

环形(首末元素是一致的)


public int rob(int[] nums) {
        if(nums.length==0)return 0;
        int n = nums.length;
        if(n==1)return nums[0];

        return Math.max(rob(nums,0,n-2),rob(nums,1,n-1)); //都是n-1的长度
    }

    public int rob(int[]nums,int first,int last){
        int pre2=0,pre1=0;
        for(int i=first;i<=last;i++){
            int cur = Math.max(pre2+nums[i],pre1);
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
    }

矩阵路径

64.最小路径和

public int minPathSum(int[][] grid) {
        if(grid.length==0||grid[0].length==0)return 0;
        int m = grid.length, n = grid[0].length; //m是纵  n是横
        int[] dp = new int[n];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
				//找出左、上元素较小的dp
				//考虑两种特殊情况 i==0  则j位置的上面元素的dp不存在
				//              j==0 则j位置的前面元素的dp不存在
                if(i==0){ 
                    dp[j]=j==0?0:dp[j-1];
                }else{
                    dp[j]=j==0?dp[j]:Math.min(dp[j-1],dp[j]);
                }
                dp[j] += grid[i][j]; //更新i,j位置的dp[j]
            }
        }
        return dp[n-1];
    }

62.不同路径
注意有个类似最短编辑距离的初始化

数组区间

413.等差数列划分

dp[i] = dp[i-1] + 1; //多了一个相同的元素目前为a+1个元素 前a个相同的元素等差组合为dp[i-1]则后a个也是dp[i-1] 再者整体a+1也会构成一个

public int numberOfArithmeticSlices(int[] nums) {
        if(nums.length==0)return 0;
        int[] dp = new int[nums.length];

        for(int i=2;i<nums.length;i++){
            if(nums[i-1]-nums[i-2]==nums[i]-nums[i-1]){
                dp[i] = dp[i-1] + 1; 
            }
        }
        int total = 0;
        for(int cnt:dp){
            total +=cnt;
        }
        return total;
    }

分割整数

343.整数拆分

public int integerBreak(int n) {
        int []dp = new int[n+1];  //n+1主要便于逻辑思考   将数与索引一致

        dp[1] = 1;
        for(int i=2;i<=n;i++){
            for(int j =1;j<i;j++){ //从i中拆出j   不断更新dp[i]
                dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j])); 
            }
        }
        return dp[n];
    }

279.完全平方数

public int numSquares(int n) {
        int[] dp = new int[n + 1]; // 默认初始化值都为0
        for (int i = 1; i <= n; i++) {
            dp[i] = i; // 最坏的情况就是每次+1
            for (int j = 1; i - j * j >= 0; j++) { 
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 动态转移方程
            }
        }
        return dp[n];
    }

public int numSquares(int n) {
        int[] dp = new int[n+1];

        List<Integer> squareList = generateSquareList(n);

        for(int i =1;i<=n;i++){
            int min = Integer.MAX_VALUE;
            for(int square:squareList){
                if(square>i){
                    break;
                }
                min = Math.min(min,dp[i-square]+1); //自下而上
            }
            dp[i] =min;
        }
        return dp[n];
    }
		//这种产生平方数列的方式还是比较有意思的
    private List<Integer> generateSquareList(int n){
        int square = 1;
        int diff = 3;
        List<Integer> list = new ArrayList<>();

        while(square<=n){
            list.add(square);
            square += diff;
            diff += 2;
        }

        return list;
    }

91.解码方法

public int numDecodings(String s) {
        if(s==null||s.length()==0)return 0;
        int n = s.length();
        int[] dp = new int[n+1];
        dp[0] =1;
        dp[1] = s.charAt(0)=='0'?0:1;

        for(int i=2;i<=n;i++){
            int one = Integer.valueOf(s.substring(i - 1, i));
            if(one!=0){
                dp[i] = dp[i-1]; //一个字符时,和前面的可能相同
            }

            if(s.charAt(i-2)=='0')continue; //两个连续字符的前一个不为'0'
            int two =Integer.valueOf(s.substring(i-2,i));
            if(two<=26){
                dp[i] +=dp[i-2]; //这儿时dp[i]+dp[i-2] 而不是dp[i-1]+dp[i-2] 在于后面的字符可能为'0'
            }
        }
        return dp[n];
    }

0-1 背包

416. 分割等和子集转换成了sum/2体积的0-1背包问题

public boolean canPartition(int[] nums) {
        int sum = computeArraySum(nums);
        if(sum%2!=0)return false;

        int W = sum/2;
        //Arrays.sort(nums);  //商品的体积要升序

        boolean[] dp = new boolean[W+1];
        dp[0] = true;  //这个0位置表示背包刚好填满,即找到这个数
        for(int num:nums){
            for(int j =W;j>=num;j--){ //将 W->1尽可能的去填充 然后策略更新
                dp[j] = dp[j] || dp[j-num];  //当j-num为0时为true,就去放 
            }
        }
        return dp[W];
    }

    private int computeArraySum(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        return sum;
    }

参照背包

public int knapsack(int W, int N, int[] weights, int[] values) {
        int[] dp = new int[W + 1];
        for (int i = 1; i <= N; i++) {
            int w = weights[i - 1], v = values[i - 1];
            for (int j = W; j >= 1; j--) { 
                if (j >= w) {
                    dp[j] = Math.max(dp[j], dp[j - w] + v);  //当dp[j]有物品了, 通过比较价值放还是不放
                }
            }
        }
        return dp[W];
    }

494. 目标和

    public int findTargetSumWays(int[] nums, int S) {
        int sum = computeTargetSum(nums);
        if(sum<S||(sum+S)%2!=0)return 0;

        int W = (sum+S)/2;
        int[] dp = new int[W+1];

        dp[0] = 1; //这个0位置表示背包刚好填满,即找到这个数  然后加上这个dp即+1
        for(int num:nums){
            for(int j =W;j>=num;j--){
                dp[j] = dp[j] + dp[j-num];
            }
        }
        return dp[W];
        
    }
    private int computeTargetSum(int[]nums){
        int sum = 0;
        for(int num:nums){
            sum += num;
        }
        return sum;
    }

139. 单词拆分

完全背包:对物品的迭代是在最里层。即物品没有次数限制 ps与416对比


public boolean wordBreak(String s, List<String> wordDict) {
        int n = s.length();

        boolean[] dp = new boolean[n+1];
        dp[0] = true;

        for(int i= 1;i<=n;i++){
            for(String word:wordDict){ //物品迭代 在里层
                int len = word.length();
                if(len<=i&&word.equals(s.substring(i-len,i))){
                    dp[i] = dp[i] || dp[i-len];
                }
            }
        }
        return dp[n];
    }

  1. 零钱兑换
  2. 组合总和 Ⅳ

474. 一和零

多维费用的 0-1 背包问题

public int findMaxForm(String[] strs, int m, int n) {
        if(strs==null||strs.length==0)return 0;

        int[][] dp =new int[m+1][n+1];

        for(String str:strs){
            int zeros=0,ones=0;
            for(char c :str.toCharArray()){
                if(c=='0'){
                    zeros++;
                }else{
                    ones++;
                }
            }

            for(int i=m;i>=zeros;i--){
                for(int j=n;j>=ones;j--){
                    dp[i][j] = Math.max(dp[i][j],dp[i-zeros][j-ones]+1);
                }
            }
        }
        return dp[m][n];
    }

股票交易

309. 最佳买卖股票时机含冷冻期

参考官方题解


public int maxProfit(int[] prices) {
        if(prices==null||prices.length==0)return 0;

        int f0 = -prices[0]; //手上持有股票的最大收益
        int f1 = 0; //手上不持有股票,并且处于冷冻期中的累计最大收益
        int f2 = 0; //手上不持有股票,并且不在冷冻期中的累计最大收益
        for(int i = 1;i<prices.length;i++){
            int newF0 = Math.max(f0,f2-prices[i]);
            int newF1 = f0+prices[i]; //卖
            int newF2 = Math.max(f2,f1);
            f0 = newF0;
            f1 = newF1;
            f2 = newF2;
        }

        return Math.max(f1,f2);
    }

714. 买卖股票的最佳时机含手续费

参照309 状态转移

    public int maxProfit(int[] prices, int fee) {
        if(prices==null||prices.length==0)return 0;
        
        int buy = -prices[0],sell = 0;
        for(int i=1;i<prices.length;i++){
            //int temp = buy;
            buy = Math.max(buy,sell-prices[i]);
            sell = Math.max(sell,buy+prices[i]-fee);
        }
        return sell;
    }

123. 买卖股票的最佳时机 III

就是围绕状态转移方程

public int maxProfit(int[] prices) {
        int buy1 = -prices[0],sell1 = 0;
        int buy2 = -prices[0],sell2 = 0;

        for(int i=1;i<prices.length;i++){
            buy1 = Math.max(buy1,-prices[i]);
            sell1 = Math.max(sell1,buy1+prices[i]);
            buy2 = Math.max(buy2,sell1-prices[i]);
            sell2 = Math.max(sell2,buy2+prices[i]);
        }

        return sell2;
    }

188. 买卖股票的最佳时机 IV

参考官方解释


public int maxProfit(int k, int[] prices) {
        if(prices==null||prices.length==0)return 0;

        int n = prices.length;

        k = Math.min(k,n/2);  //买,卖是两天的事

        int[] buy = new int[k+1];
        int[] sell = new int[k+1];
        
        buy[0] = -prices[0];
        sell[0] = 0;
        for(int i=1;i<=k;i++){
            buy[i] = sell[i] = Integer.MIN_VALUE / 2;
        }

        for(int i=1;i<n;i++){
            buy[0] = Math.max(buy[0],sell[0]-prices[i]);
            for(int j=1;j<=k;j++){
                //两种可能  i-1天就进行了j笔交易持有股  或不持有股(得买
                buy[j] = Math.max(buy[j],sell[j]-prices[i]); 
                //两种可能  i-1天就进行了j笔交易    i-1天进行了j-1笔交易还持有股(得卖
                sell[j] = Math.max(sell[j],buy[j-1]+prices[i]); 
            }
        }

        return Arrays.stream(sell).max().getAsInt(); //不是交易越多越好所以整体取最大

    }

子序列

300.最长递增子序列

    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        for(int i =0;i<nums.length;i++){
            int max = 1;
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){ //由于这个条件,所以最后一个dp并不是最大
                    max = Math.max(max,dp[j]+1);
                }  
            }
            dp[i] =max; //将每个位置的记录下
        }
        int ret = 0;
        for (int i = 0; i < n; i++) {
            ret = Math.max(ret, dp[i]);
        }
        return ret;
    }

646.最长数对链
扎气球问题

376. 摆动序列

public int wiggleMaxLength(int[] nums) {
        if(nums==null||nums.length==0)return 0;
        
        int up =1,down =1;
        for(int i=1;i<nums.length;i++){
            if(nums[i]>nums[i-1]){
                up = down + 1;  //这样写其实是在累积  题目说可以人为删除不是的
            }
            if(nums[i]<nums[i-1]){
                down = up+1;
            }
        }

        return Math.max(up,down);
    }

1143.最长公共子序列

public int longestCommonSubsequence(String text1, String text2) {
        if(text1.length()==0||text2.length()==0)return 0;

        int n1 = text1.length(), n2 = text2.length();
        int[][] dp = new int[n1+1][n2+1];

        for(int i=1;i<=n1;i++){
            for(int j=1;j<=n2;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][j-1],dp[i-1][j]);
                }
            }
        }
        return dp[n1][n2];
    }

字符串编辑

  1. 两个字符串的删除操作
    最长公共子字符串的变种

72. 编辑距离

最长公共字符串的变种 PS:船名字符识别项目就是拿编辑距离度量的


 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=1;i<=m;i++){
            dp[i][0] = i;
        }
        for(int i=1;i<=n;i++){
            dp[0][i] = i;
        }

        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];
    }

10. 正则表达式匹配

在这里插入图片描述在这里插入图片描述

public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();

        boolean[][]dp = new boolean[m+1][n+1];
        dp[0][0] =true;
        for(int i=0;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(p.charAt(j-1)=='*'){  //判断第j个字符
                    dp[i][j] = dp[i][j-2];  //先默认匹配空
                    if(matches(s,p,i,j-1)){  //得看*的前一个字符是否匹配
                        dp[i][j] = dp[i][j]||dp[i-1][j]; // dp[i-1][j]指*匹配多个字符
                    }
                }else{
                    if(matches(s,p,i,j)){
                        dp[i][j] = dp[i-1][j-1]; //
                    }
                }
            }
        }
        return dp[m][n];
    }
    private boolean matches(String s,String p,int i,int j){
        if(i==0||j==0){
            return false;
        }
        if(p.charAt(j-1)=='.'){
            return true;
        }
        return p.charAt(j-1)==s.charAt(i-1);
    }

44. 通配符匹配

在这里插入图片描述

class Solution {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();
        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        for (int i = 1; i <= n; ++i) {
            if (p.charAt(i - 1) == '*') {
                dp[0][i] = true;
            } else {
                break;
            }
        }
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                } else if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                }
            }
        }
        return dp[m][n];
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/wildcard-matching/solution/tong-pei-fu-pi-pei-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

650. 只有两个键的键盘(复制粘贴字符)

    public int minSteps(int n) {
        if(n==1)return 0;

        for(int i=2;i<=Math.sqrt(n);i++){   
            if(n%i==0)return i+minSteps(n/i); //分堆
        }

        return n;
    }

数学

204. 计数质数

埃氏筛:参考官方解读

public int countPrimes(int n) {
        boolean[] notPrimes = new boolean[n+1];
        int cnt =0;
        for(int i=2;i<n;i++){
            if(notPrimes[i]){
                continue;
            }
            cnt ++;
            for(long j=(long)(i)*i;j<n;j+=i){
                notPrimes[(int)j] = true;
            }
            
        }
        return cnt; 
    }

传统写法

public int countPrimes(int n) {
        int cnt =0;
        
        for(int i=2;i<n;i++){
            cnt += isPrime(i)?1:0;
        }
        return cnt; 
    }

    private boolean isPrime(int x){
        for(int i =2;i*i<=x;i++){
            if(x%i==0){
                return false;
            }
        }
        return true;
    }

172. 阶乘后的零

这里是阶乘贡献5

思路:先整体从含有5的数中各抽取一个5即n/5 可能有的数不止一个5 即n/5>=5 在对不止一个5的数再抽取一个 (n/5)/5 一次进行下去
同理:统计的是 N! 的二进制表示中最低位 1 的位置,只要统计有多少个 2 即可

//非递归方式
public int trailingZeroes(int n) {
        int zeroCnt = 0;
        while(n>0){
            n /=5;
            zeroCnt += n ;
        }
        return zeroCnt;
    }
//递归方式
public int trailingZeroes(int n) {
        return n==0?0:n/5+ trailingZeroes(n/5);
    }
  1. 数字转换为十六进制数
public String toHex(int num) {
        if(num==0)return "0";
        char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

        StringBuilder strBuild = new StringBuilder();
        while(num!=0){
            strBuild.append(map[num&0b1111]);
            num >>>= 4;
        }
        return strBuild.reverse().toString();
    }

Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。

public String convertToBase16(int num) {
return Integer.toString(num, 16);
}

415. 字符串相加

 public String addStrings(String num1, String num2) {
        int n1 = num1.length()-1, n2 = num2.length()-1;
        int carry = 0;

        StringBuilder strBuild = new StringBuilder();
        while(carry!=0||n1>=0||n2>=0){
            if(n1>=0){
                carry+=num1.charAt(n1--)-'0';  //低位为字符串长度-1索引的字符
            }
            if(n2>=0){
                carry+=num2.charAt(n2--)-'0';
            }

            strBuild.append(carry%10);

            carry/=10;
        }

        return strBuild.reverse().toString();
    }

  1. 二进制求和

462. 最少移动次数使数组元素相等 II

相遇问题:移动距离最小的方式是所有元素都移动到中位数

public int minMoves2(int[] nums) {
        Arrays.sort(nums);
        int l =0, h=nums.length - 1;
        int moves = 0;
        while(l<h){
            moves += nums[h] - nums[l];
            l++;
            h--;
        }
        return moves;
    }

优化:快速选择

238. 除自身以外数组的乘积

public int[] productExceptSelf(int[] nums) {
        //主要是为了构建上下三角矩阵 当前元素位置为1
        int n = nums.length;
        int[] products = new int[n];
        Arrays.fill(products,1);

        //下三角
        int left = 1;
        for(int i =1;i<n;i++){   //对于下三角累积i = 0不用考虑
            left *= nums[i-1];  //累积元素,不能累积自己  向左错开一位
            products[i] *= left;
        }

        //上三角
        int right = 1;
        for(int i=n-2;i>=0;i--){  //对于上三角累积i=n-1不用考虑
            right *= nums[i+1];  //累积元素,不能累积自己  向右错开一位
            products[i] *=right;
        }

        return products;

    }

排序

215. 数组中的第K个最大元素

参考link

切分

import java.util.Random;

class Solution {
    private static Random random = new Random(System.currentTimeMillis());
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;

        // 转换一下,第 k 大元素的索引是 len - k
        int target = len - k;

        while (true) {
            int index = partition(nums, left, right);
            if (index == target) {
                return nums[index];
            } else if (index < target) {
                left = index + 1;
            } else {
                right = index - 1;
            }
        }
    }

    /**
     * 在数组 nums 的子区间 [left, right] 执行 partition 操作,返回 nums[left] 排序以后应该在的位置
     * 在遍历过程中保持循环不变量的语义
     * 1、[left + 1, j] < nums[left]
     * 2、(j, i] >= nums[left]
     *
     * @param nums
     * @param left
     * @param right
     * @return
     */
    public int partition(int[] nums, int left, int right) {
        // 在区间随机选择一个元素作为标定点
        if (right > left) {
            int randomIndex = left + 1 + random.nextInt(right - left);
            swap(nums, left, randomIndex);
        }

        int pivot = nums[left];
        int j = left;
        for (int i = left + 1; i <= right; i++) {
            if (nums[i] < pivot) {
                // 小于 pivot 的元素都被交换到前面
                j++;
                swap(nums, j, i);
            }
        }
        // 在之前遍历的过程中,满足 [left + 1, j] < pivot,并且 (j, i] >= pivot
        swap(nums, j, left); //此时j位置的值比pivot小
        // 交换以后 [left, j - 1] < pivot, nums[j] = pivot, [j + 1, right] >= pivot
        return j;
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

堆排

import java.util.PriorityQueue;

public class Solution {

    // 根据 k 的不同,选最大堆和最小堆,目的是让堆中的元素更小
    // 思路 1:k 要是更靠近 0 的话,此时 k 是一个较小的数,用最大堆
    // 例如在一个有 6 个元素的数组里找第 5 大的元素
    // 思路 2:k 要是更靠近 len 的话,用最小堆

    // 所以分界点就是 k = len - k

    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        if (k <= len - k) {
            // System.out.println("使用最小堆");
            // 特例:k = 1,用容量为 k 的最小堆
            // 使用一个含有 k 个元素的最小堆
            PriorityQueue<Integer> minHeap = new PriorityQueue<>(k, (a, b) -> a - b);
            for (int i = 0; i < k; i++) {
                minHeap.add(nums[i]);
            }
            for (int i = k; i < len; i++) {
                // 看一眼,不拿出,因为有可能没有必要替换
                Integer topEle = minHeap.peek();
                // 只要当前遍历的元素比堆顶元素大,堆顶弹出,遍历的元素进去
                if (nums[i] > topEle) {
                    minHeap.poll();
                    minHeap.add(nums[i]);
                }
            }
            return minHeap.peek();

        } else {
            // System.out.println("使用最大堆");
            assert k > len - k;
            // 特例:k = 100,用容量为 len - k + 1 的最大堆
            int capacity = len - k + 1;
            PriorityQueue<Integer> maxHeap = new PriorityQueue<>(capacity, (a, b) -> b - a);
            for (int i = 0; i < capacity; i++) {
                maxHeap.add(nums[i]);
            }
            for (int i = capacity; i < len; i++) {
                // 看一眼,不拿出,因为有可能没有必要替换
                Integer topEle = maxHeap.peek();
                // 只要当前遍历的元素比堆顶元素小,堆顶弹出,遍历的元素进去
                if (nums[i] < topEle) {
                    maxHeap.poll();
                    maxHeap.add(nums[i]);
                }
            }
            return maxHeap.peek();
        }
    }
}

347. 前 K 个高频元素

桶排序(推荐)

public int[] topKFrequent(int[] nums, int k) {
        // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        HashMap<Integer,Integer> map = new HashMap<>();
        for(int num:nums){
            if (map.containsKey(num)) {
                map.put(num,map.get(num)+1);
            }else{
                map.put(num,1);
            }
        }

        //桶排序
        //将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标
        List<Integer>[] list = new ArrayList[nums.length+1];
        for(int key:map.keySet()){
            int cnt = map.get(key);
            if(list[cnt]==null){
                list[cnt] = new ArrayList();  //用于存储出现次数相同的多个数字
            }
            list[cnt].add(key); //将数字给加进来
        }
        
        List<Integer> res = new ArrayList();
         // 倒序遍历数组获取出现次数从大到小的排列
        for(int i=list.length-1;i>=0&&res.size()<k;i--){
            if(list[i]==null)continue;
            res.addAll(list[i]);
        }
        return res.stream().mapToInt(i->i).toArray(); //将List<Integer>转为int[]
    }

堆排序

public List<Integer> topKFrequent(int[] nums, int k) {
        // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        HashMap<Integer,Integer> map = new HashMap();
        for(int num : nums){
            if (map.containsKey(num)) {
               map.put(num, map.get(num) + 1);
             } else {
                map.put(num, 1);
             }
        }
        // 遍历map,用最小堆保存频率最大的k个元素
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return map.get(a) - map.get(b);
            }
        });
        for (Integer key : map.keySet()) {
            if (pq.size() < k) {
                pq.add(key);
            } else if (map.get(key) > map.get(pq.peek())) {
                pq.remove();
                pq.add(key);
            }
        }
        // 取出最小堆中的元素
        List<Integer> res = new ArrayList<>();
        while (!pq.isEmpty()) {
            res.add(pq.remove());
        }
        return res.stream().mapToInt(i->i).toArray(); //将List<Integer>转为int[]
    }

  1. 根据字符出现频率排序

912.快速排序递归与非递归实现

import java.util.Random;

public class Solution {

    // 快速排序 2:双指针(指针对撞)快速排序

    /**
     * 列表大小等于或小于该大小,将优先于 quickSort 使用插入排序
     */
    private static final int INSERTION_SORT_THRESHOLD = 7;

    private static final Random RANDOM = new Random();

    public int[] sortArray(int[] nums) {
        int len = nums.length;
        quickSort(nums, 0, len - 1);
        return nums;
    }

    private void quickSort(int[] nums, int left, int right) {
        // 小区间使用插入排序
        if (right - left <= INSERTION_SORT_THRESHOLD) {
            insertionSort(nums, left, right);
            return;
        }

        int pIndex = partition(nums, left, right);
        quickSort(nums, left, pIndex - 1);
        quickSort(nums, pIndex + 1, right);
    }

    /**
     * 对数组 nums 的子区间 [left, right] 使用插入排序
     *
     * @param nums  给定数组
     * @param left  左边界,能取到
     * @param right 右边界,能取到
     */
    private void insertionSort(int[] nums, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int temp = nums[i];
            int j = i;
            while (j > left && nums[j - 1] > temp) {
                nums[j] = nums[j - 1];
                j--;
            }
            nums[j] = temp;
        }
    }

    private int partition(int[] nums, int left, int right) {
        int randomIndex = left + RANDOM.nextInt(right - left + 1);
        swap(nums, randomIndex, left);

        int pivot = nums[left];
        int lt = left + 1;
        int gt = right;

        // 循环不变量:
        // all in [left + 1, lt) <= pivot
        // all in (gt, right] >= pivot
        while (true) {
            while (lt <= right && nums[lt] < pivot) {
                lt++;
            }

            while (gt > left && nums[gt] > pivot) {
                gt--;
            }

            if (lt >= gt) {
                break;
            }

            // 细节:相等的元素通过交换,等概率分到数组的两边
            swap(nums, lt, gt);
            lt++;
            gt--;
        }
        swap(nums, left, gt);
        return gt;
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

非递归

    private void quickSort(int[] nums) {
        LinkedList<Integer>stack = new LinkedList<>();
        stack.push(0);
        stack.push(nums.length-1);

        while(!stack.isEmpty()){
            int right = stack.pop();
            int left = stack.pop();
            if(left<right){
                int pIndex = partition(nums, left, right); //排序的核心换种方式鞭策
                if(left<pIndex){
                    stack.push(left);
                    stack.push(pIndex-1);
                }
                if(right>pIndex){
                    stack.push(pIndex+1);
                    stack.push(right);
                }   
            }
        } 
    }

75. 颜色分类

链接

三向切分

public void sortColors(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return;
        }
        // all in [0, zero] = 0
        // all in (zero, i) = 1
        // all in (two, len - 1] = 2

        // 为了保证初始化的时候 [0, zero] 为空,设置 zero = -1,
        // 所以下面遍历到 0 的时候,先加,再交换
        int zero = -1;

        // 为了保证初始化的时候 (two, len - 1] 为空,设置 two = len - 1
        // 所以下面遍历到 2 的时候,先交换,再减
        int two = len - 1;
        int i = 0;
        // 当 i == two 的时候,还有一个元素还没有看,
        // 因此,循环可以继续的条件是 i <= two
        while (i <= two) {
            if (nums[i] == 0) {
                zero++;
                swap(nums, i, zero);
                i++;
            } else if (nums[i] == 1) {
                i++;
            } else {
                swap(nums, i, two);
                two--;
            }
        }
    }

    private void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }

二分查找

int mid = l+(h-l)/2;
int sqrt = x/mid;
查找target最先出现的位置

public int binarySearch(int[] nums, int key) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int m = l + (h - l) / 2;
        if (nums[m] >= key) {
            h = m;
        } else {
            l = m + 1;
        }
    }
    return l;
}

69. x 的平方根

public int mySqrt(int x) {
        if(x<=1)return x;

        int l = 1, h = x;
        while(l<=h){
            int mid = l+(h-l)/2;
            int sqrt = x/mid;  //sqrt*sqrt = x;
            if(mid==sqrt){  //只有x是整数的平方 才在这里返回
                return mid;
            }else if(mid>sqrt){
                h = mid -1;
            }else{
                l = mid +1;
            }
        }
        return h;  //取出小数的整数部分返回
    }

744. 寻找比目标字母大的最小字母

public char nextGreatestLetter(char[] letters, char target) {
        int n = letters.length;
        int l = 0, h = n-1;
        while(l<=h){
            int mid = l + (h-l)/2;
            if(letters[mid]<=target){
                //有两种情况
                //1.letters[mid] = target  则letters[mid+1]恰好>target  之后就是不断的更新h
                //2.letters[mid] < target可能letters[mid+1]恰好>target(之后就是不断更新h)
                //                       也可能不是这么回事儿(之后还是得找能使letters[mid+1]恰好>target的mid),最终可能因为找不到l超出长度
                l = mid +1;
            }else{
                h = mid - 1;
            }
        }

        return l<n?letters[l]:letters[0];
    }

34. 在排序数组中查找元素的第一个和最后一个位置

结合744. 寻找比目标字母大的最小字母 找出last再-1

 public int[] searchRange(int[] nums, int target) {
        int first = binarySearch(nums, target);
        int last = binarySearch(nums, target + 1) - 1;
        if (first == nums.length || nums[first] != target) {
            return new int[]{-1, -1};
        } else {
            return new int[]{first, Math.max(first, last)};
        }
    }

    //找出在最先出现target的索引   没找到返回总长度
    private int binarySearch(int[] nums, int target) {
        int l = 0, h = nums.length; // 正常是nums.length-1
        //有这么种情况,target+1不存在,即数组中值一只小,则l就等于h跳出循环
        while (l < h) {
            int m = l + (h - l) / 2;
            if (nums[m] >= target) {
                h = m;
            } else {
                l = m + 1;
            }
        }
        return l;
    }

540. 有序数组中的单一元素

public int singleNonDuplicate(int[] nums) {
        int l = 0,h = nums.length-1;
        while(l<h){  // h 的赋值表达式为 h = m,那么循环条件也就只能使用 l < h 这种形式
            int m = l+(h-l)/2;
            if(m%2==1){
                m --;
            }
            if(nums[m]==nums[m+1]){  //<=m+1没有
                l = m+2; //m+2存在嫌疑
            }else{
                h = m;
            }
        }
        return nums[l];
    }

153. 寻找旋转排序数组中的最小值

public int findMin(int[] nums) {
        int l = 0, h=nums.length-1;

        while(l<h){
            int m = l+(h-l)/2;
            if(nums[m]<=nums[h]){ //说明这一截没问题
                h = m;
            }else{
                l = m+1;
            }
        }
        return nums[l];
    }

4. 寻找两个正序数组的中位数

分割线 人为规定中位数在分割线左边

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if(nums1.length>nums2.length){
            int[] temp = nums2;
            nums2 = nums1;
            nums1 = temp;
        }
        int m = nums1.length;
        int n = nums2.length;
    
        int totalLeft = (m+n+1)/2;
        int left = 0;
        int right = m;

        while(left<right){  //二分查找
            int i = left+ (right-left+1)/2;
            int j = totalLeft - i;
            if(nums1[i-1]<=nums2[j]){ //找到最右边的那个满足条件的
                left = i;
            }else{
                right = i-1;
            }
        }
        int i = left;
        int j = totalLeft - i;

        int leftNums1Max = i==0?Integer.MIN_VALUE:nums1[i-1];
        int leftNums2Max = j==0?Integer.MIN_VALUE:nums2[j-1];
        int rightNums1Min = i==m?Integer.MAX_VALUE:nums1[i];
        int rightNums2Min = j==n?Integer.MAX_VALUE:nums2[j];

        if((m+n)%2==1){//奇数
            return Math.max(leftNums1Max,leftNums2Max);            
        }else{
            return (double)(Math.max(leftNums1Max,leftNums2Max)+Math.min(rightNums1Min,rightNums2Min))/2;
        }

    }

搜索

BFS

使用 BFS 只能求解无权图的最短路径。

在程序实现 BFS 时需要考虑以下问题:
队列:用来存储每一轮遍历得到的节点;
标记:对于遍历过的节点,应该将它标记,防止重复遍历

127. 单词接龙

 public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        // 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
        Set<String> wordSet = new HashSet<>(wordList);
        if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
            return 0;
        }
        wordSet.remove(beginWord);
        
        // 第 2 步:图的广度优先遍历,必须使用队列和表示是否访问过的 visited 哈希表
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);
        
        // 第 3 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1
        int step = 1;
        while (!queue.isEmpty()) {
            int currentSize = queue.size();
            for (int i = 0; i < currentSize; i++) {
                // 依次遍历当前队列中的单词
                String currentWord = queue.poll();
                // 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1
                if (changeWordEveryOneLetter(currentWord, endWord, queue, visited, wordSet)) {
                    return step + 1;
                }
            }
            step++;
        }
        return 0;
    }

    /**
     * 尝试对 currentWord 修改每一个字符,看看是不是能与 endWord 匹配
     *
     * @param currentWord
     * @param endWord
     * @param queue
     * @param visited
     * @param wordSet
     * @return
     */
    private boolean changeWordEveryOneLetter(String currentWord, String endWord,
                                             Queue<String> queue, Set<String> visited, Set<String> wordSet) {
        char[] charArray = currentWord.toCharArray();
        for (int i = 0; i < endWord.length(); i++) {
            // 先保存,然后恢复
            char originChar = charArray[i];
            for (char k = 'a'; k <= 'z'; k++) {
                if (k == originChar) {
                    continue;
                }
                charArray[i] = k;
                String nextWord = String.valueOf(charArray);
                if (wordSet.contains(nextWord)) {
                    if (nextWord.equals(endWord)) {
                        return true;
                    }
                    if (!visited.contains(nextWord)) {
                        queue.add(nextWord);
                        // 注意:添加到队列以后,必须马上标记为已经访问
                        visited.add(nextWord);
                    }
                }
            }
            // 恢复
            charArray[i] = originChar;
        }
        return false;
    }

DFS

DFS 常用来求解这种 可达性 问题。

在程序实现 DFS 时需要考虑以下问题:
栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。

695. 岛屿的最大面积

沉岛思想


public int maxAreaOfIsland(int[][] grid) {
        if(grid==null||grid[0]==null) return 0;
        int maxArea = 0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                maxArea = Math.max(maxArea,dfs(i,j,grid));
            }
        }
        return maxArea;
    }
    private int dfs(int i,int j,int[][] grid){ //返回当前岛面积
        if(i<0||j<0||i>grid.length-1||j>grid[0].length-1||grid[i][j]==0){
            return 0;
        }
        grid[i][j] = 0; //湮灭掉 要不就重复了
        int area = 1;

        //环顾四周的面积
        area +=dfs(i+1,j,grid); //上
        area +=dfs(i-1,j,grid); //下
        area +=dfs(i,j-1,grid); //左
        area +=dfs(i,j+1,grid); //右

        return area;
    }

200. 岛屿数量

public int numIslands(char[][] grid) {
        if(grid==null||grid[0]==null){
            return 0;
        }

        int isLandNums = 0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){  
                    isLandNums ++;
                    dfs(grid,i,j); //出现一个'1'将这一片相连的都置为0了
                }
            }
        }

        return isLandNums;
    }
    private void dfs(char[][]grid,int nr,int nc){
        if(nc<0||nr<0||nr>grid.length-1||nc>grid[0].length-1||grid[nr][nc]=='0'){
            return;
        }

        grid[nr][nc] = '0'; //沉没掉

        dfs(grid,nr+1,nc);
        dfs(grid,nr-1,nc);
        dfs(grid,nr,nc+1);
        dfs(grid,nr,nc-1);
    }

463. 岛屿的周长

DFS 寻找天涯海角 不是啥时小岛都可以沉没

public int islandPerimeter(int[][] grid) {
        if(grid==null||grid[0]==null){
            return 0;
        }

        int ans = 0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]==1){
                    ans +=dfs(grid,i,j); //出现一个1将这一片相连的都置为2了,不能置为0
                }
            }
        }
        return ans;
    }

    private int dfs(int[][] grid,int r,int c){
        if(r<0||c<0||r>grid.length-1||c>grid[0].length-1||grid[r][c]==0){
            return 1;   //在岛上碰壁 遇到区域边缘或者海水
        }

        if(grid[r][c]==2){  //遇到2是标记过的陆地
            return 0;
        }

        grid[r][c] = 2;  //不能乱沉没了

        int res = 0;

        //看看四周是不是天涯海角
        res += dfs(grid,r+1,c);
        res += dfs(grid,r-1,c);
        res += dfs(grid,r,c+1);
        res += dfs(grid,r,c-1);

        return res;
    }

130. 被围绕的区域

这题和463有点像 设计到岛屿与边界相连

public void solve(char[][] board) {
        if(board==null||board[0]==null)return;

        int nr = board.length, nc = board[0].length;
        //找出连接四周的小岛,并标记
        for(int i=0;i<nr;i++){
            dfs(board,i,0);
            dfs(board,i,nc-1);
        }
        for(int i=0;i<nc;i++){
            dfs(board,0,i);
            dfs(board,nr-1,i);
        }

        
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                if(board[i][j]=='O'){ //更改
                    board[i][j] = 'X';
                }
                if(board[i][j]=='A'){
                    board[i][j] = 'O'; //恢复
                }
            }
        }

    }
    private void dfs(char[][]board,int r,int c){
        if(r<0||c<0||r>=board.length||c>=board[0].length||board[r][c]!='O'){
            return; 
        }

        board[r][c] = 'A';  //与边界相连的小岛给个独特标志

        dfs(board,r+1,c);
        dfs(board,r-1,c);
        dfs(board,r,c+1);
        dfs(board,r,c-1);

    }

547. 省份数量

public int findCircleNum(int[][] isConnected) {
        if(isConnected==null||isConnected[0]==null)return 0;

        int n = isConnected.length;
        boolean[] visited = new boolean[n];
        int count = 0;
        for(int i=0;i<n;i++){
            if(!visited[i]){ //类似于海岛个数  只不过这个用了boolean[]来标记遍历过位置
                dfs(isConnected,i,visited);
                count ++; 
            }
        }
        return count;
    }
    private void dfs(int[][]isConnected,int i,boolean[] visited){
        visited[i] = true; //记录一下遍历过了
        for(int j=0;j<isConnected.length;j++){ //找与i有关联且没有遍历过的j
            if(isConnected[i][j]==1&&!visited[j]){
                dfs(isConnected,j,visited);
            }
        }
    }

417. 太平洋大西洋水流问题

浪潮思想 可达交汇


//由于当前得与后一个值比较所以得用方向数组
    private int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        if(heights==null||heights[0]==null){
            return null;
        }

        int nr = heights.length;
        int nc = heights[0].length;

        //可达记录
        boolean[][] reachP = new boolean[nr][nc];
        boolean[][] reachA = new boolean[nr][nc];

        //浪潮思想  太平洋与大西洋分别义两个边为起点  类似题130
        for(int i=0;i<nr;i++){
            dfs(heights,i,0,reachP);
            dfs(heights,i,nc-1,reachA);
        }

        for(int i=0;i<nc;i++){
            dfs(heights,0,i,reachP);
            dfs(heights,nr-1,i,reachA);
        }

        List<List<Integer>> lists = new ArrayList<>();
        
        List<Integer> list = null;
        for(int i=0;i<nr;i++){
            for(int j=0;j<nc;j++){
                //寻找交集  即都可达的陆地
                if(reachP[i][j]&&reachA[i][j]){
                    list = new ArrayList<>();
                    list.add(i);
                    list.add(j);
                    lists.add(list);
                }
            }
        }

        return lists;

    }

    private void dfs(int[][]heights,int r,int c,boolean[][] reach){
        if(reach[r][c])return;

        reach[r][c] = true;  //标记下
        for(int[]direction:directions){
            int nextR = r + direction[0];
            int nextC = c +direction[1];
            if(nextR<0||nextC<0||nextR>=heights.length||nextC>=heights[0].length||heights[nextR][nextC]<heights[r][c]){
                continue;
            }
            dfs(heights,nextR,nextC,reach);
        }
        
    }

Backtracking

Backtracking(回溯)属于 DFS。

普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
而 Backtracking 主要用于求解 排列组合 问题,例如有 { 'a','b','c' } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即就返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:

在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。

17. 电话号码的字母组合


public List<String> letterCombinations(String digits) {
        List<String> combinations = new ArrayList<>();
        if(digits.length()==0)return combinations;
        
        Map<Character,String> map = new HashMap<>(){{
            put('2', "abc");
            put('3', "def");
            put('4', "ghi");
            put('5', "jkl");
            put('6', "mno");
            put('7', "pqrs");
            put('8', "tuv");
            put('9', "wxyz");
        }};
        backTracking(combinations,new StringBuilder(),0,digits,map);

        return combinations;
    }
    private void backTracking(List<String> combinations,StringBuilder combination,int index,String digits,Map<Character,String> map){
        if(combination.length()==digits.length()){
            combinations.add(combination.toString()); //找到便回退
        }else{
            String letters = map.get(digits.charAt(index));
            for(int i=0;i<letters.length();i++){
                combination.append(letters.charAt(i)); //添加
                backTracking(combinations,combination,index+1,digits,map); //这个index+1去下个号码
                combination.deleteCharAt(index); //回退后删除 让当前级别的其它字符填充
            }
        }
    }

79. 单词搜索

private int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};
    public boolean exist(char[][] board, String word) {
        if(word==null)return true;

        if(board==null||board[0]==null)return false;

        int nr = board.length,nc = board[0].length;
        boolean[][] visited = new boolean[nr][nc];

        for(int i=0;i<nr;i++){
            for(int j=0;j<nc;j++){
                if(backTracking(board,word,0,i,j,visited)){
                    return true;
                }
            }
        }
        return false;
    }

    private boolean backTracking(char[][] board,String word,int len,int r,int c,boolean[][] visited){
        if(len==word.length()){
            return true;
        }

        if(r<0||c<0||r>=board.length||c>=board[0].length||word.charAt(len)!=board[r][c]||visited[r][c]){
            return false;
        }
        visited[r][c] = true;  //限制当前的递归链不可重复调用该

        for(int[]direction:directions){
            int nextR = r + direction[0];
            int nextC = c + direction[1];
            if(backTracking(board,word,len+1,nextR,nextC,visited)){
                return true;
            }
        }
        visited[r][c] = false;  //回退 取消限制

        return false;
    }

257. 二叉树的所有路径

public List<String> binaryTreePaths(TreeNode root) {
        List<String> list= new ArrayList<>();
        backTracking(root,list,"");
        return list;
    }   

    public void backTracking(TreeNode node,List<String> list,String path){
        if(node!=null){
            StringBuilder pathSB = new StringBuilder(path);
            pathSB.append(Integer.toString(node.val));

            if(node.left==null&&node.right==null){
                list.add(pathSB.toString());
            }else{
                pathSB.append("->");
                backTracking(node.left,list,pathSB.toString());
                backTracking(node.right,list,pathSB.toString());
            }
        }
    }

排列回溯

列表中元素的位置不同,这两个就是不同的结果

46. 全排列

不同元素的

public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        boolean[] visited = new boolean[nums.length];
        List<Integer>list = new ArrayList<>();
        backTracking(nums,lists,list,visited);
        return lists;
    }

    private void backTracking(int[]nums,List<List<Integer>> lists,List<Integer> list,boolean[] visited){
        if(list.size()==nums.length){
             lists.add(new ArrayList<>(list));
        }

        for(int i=0;i<nums.length;i++){
            if(visited[i])continue;

            visited[i] = true; //添加
            list.add(nums[i]);
            backTracking(nums,lists,list,visited);

            list.remove(list.size()-1); //当前位置用别的元素填充  
            visited[i] = false; //删除 当前元素可填充到别的位置
        }
    }

47. 全排列 II

有重复元素的

对比题46

public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> lists = new ArrayList<>();
        boolean[] visited = new boolean[nums.length];
        List<Integer>list = new ArrayList<>();
        Arrays.sort(nums); //不同点1
        backTracking(nums,lists,list,visited);
        return lists;
    }
    private void backTracking(int[]nums,List<List<Integer>> lists,List<Integer> list,boolean[] visited){
        if(list.size()==nums.length){
             lists.add(new ArrayList<>(list));
        }

        for(int i=0;i<nums.length;i++){
            if(visited[i])continue;

            //前一个相同元素是使用了然后被回退取消了,这下不能再放个相同的元素 要不就重复了
            if(i>0&&nums[i]==nums[i-1]&&!visited[i-1]){  //不同点2
                continue;
            }

            visited[i] = true; //添加
            list.add(nums[i]);
            backTracking(nums,lists,list,visited);

            list.remove(list.size()-1); //当前位置用别的元素填充  
            visited[i] = false; //删除 当前元素可填充到别的位置
        }
    }

组合回溯

77. 组合

这题是组合问题 不是排列问题 所以和元素顺序没有关系

组合与排列的唯一区别:for循环有起始点start  即for(int i=start;i<=n;i++)
没有visited访问记录数组  这样做的目的前面的元素得先使用(可连续使用),使用过了不能再在后面使用
原始函数:backTracking(List<List<Integer>> lists,List<Integer> list,int start,int n,int k)内部调用
backTracking(lists,list,i+1,n,k);  //这里的i+1起始限制  之后的数只能是后面位置的数
backTracking(lists,list,i,n,k);  //这里的i起始限制  之后的数可能是当前位置(即可连续使用)或后面位置的数

回溯+剪支

 public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> lists = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        backTracking(lists,list,1,n,k);
        return lists;
    }

    private void backTracking(List<List<Integer>> lists,List<Integer> list,int start,int n,int k){
        if(list.size()==k){
            lists.add(new ArrayList<>(list));
						return;
        }
  
        //for(int i=start;i<=n;i++){
			 //优化:分析搜索起点的上界进行剪枝   //起点太靠后,元素根本不够,直接不用搜
		for(int i=start;i<=n-(k-list.size())+1;i++){	
					
            list.add(i);
					  //修改start进行剪支
            backTracking(lists,list,i+1,n,k);  //这里的i+1限制  之后的数只能是后面位置的数
            list.remove(list.size()-1);
        }
    }

39. 组合总和

结合77 唯一不同本题当前元素可以连续用多次

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> lists = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        backTracking(lists,list,candidates,target,0);
        return lists;
    }

    private void backTracking(List<List<Integer>> lists,List<Integer> list,int[] candidates, int target,int begin){
        if(target==0){
            lists.add(new ArrayList<>(list));
            return;
        }
        if(target<0)return;

        
        for(int i=begin;i<candidates.length;i++){
            list.add(candidates[i]);
            backTracking(lists,list,candidates,target-candidates[i],i);  //这里的i限制之后的数只能是当前或者后面位置的数
            list.remove(list.size()-1);
        }
    }
}

  1. 子集

40. 组合总和 II

结合全排列的 题47
不同元素的组合问题 得用visited 目的在于看前一位置的相同值元素是否访问回退为false了,若是的话当前这个相同值元素跳过,要不组合结果就重复了。若仍然为true这时可以添加上去即表现为连续的字符不过来自于不同位置

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> lists = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        Arrays.sort(candidates); //有重复得排序
        backTracking(lists,list,new boolean[candidates.length],candidates,target,0);
        return lists;
    }
     private void backTracking(List<List<Integer>> lists,List<Integer> list,boolean[] visited,int[] candidates, int target,int begin){
        if(target==0){
            lists.add(new ArrayList<>(list));
            return;
        }
        if(target<0)return;


        for(int i=begin;i<candidates.length;i++){
            if(i!=0&&candidates[i]==candidates[i-1]&&!visited[i-1])continue;//前一个相同元素访问过了,且已经被回退  如果没有被回退还是可以访问的
            visited[i] = true;
            list.add(candidates[i]);
            backTracking(lists,list,visited,candidates,target-candidates[i],i+1);  //这里的i+1限制之后的数只能是后面位置的数
            list.remove(list.size()-1);
            visited[i] = false;
        }
    }
}

  1. 子集 II

216. 组合总和 III


public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> lists = new ArrayList<>();
        backTracking(lists,new ArrayList<>(),n,1,k);

        return lists;
    }

    private void backTracking(List<List<Integer>> lists,List<Integer> list, int target,int begin,int k){
        if(list.size()==k&&target==0){
            lists.add(new ArrayList<>(list));
            return;
        }
        if(list.size()==k||target==0)return; //要么数个数到了,可能没达到目标, 要么是达到目标了

        
        for(int i=begin;i<=9;i++){
            list.add(i);
            backTracking(lists,list,target-i,i+1,k);  //这里的i限制之后的数只能是后面位置的数
            list.remove(list.size()-1);
        }
    }

字符串回溯分割

93. 复原 IP 地址

public List<String> restoreIpAddresses(String s) {
        List<String> addresses = new ArrayList<>();
        if(s.length()<4)return addresses;

        StringBuilder address = new StringBuilder();
        backTracking(addresses,address,0,s);

        return addresses;
    }

    public void backTracking(List<String> addresses,StringBuilder address,int k,String s){
        if(k==4||s.length()==0){  // k表示有几段
            if(k==4&&s.length()==0){
            addresses.add(address.toString());  //
            }
        }else{
            for(int i=0;i<s.length()&&i<=2;i++){
                if(i!=0&&s.charAt(0)=='0')break; //不能以'0'开头分割  单独一个0还是可以的
                String part = s.substring(0,i+1); //截取前0~i个字符
                if(Integer.valueOf(part)<=255){
                    part = (k!=0?".":"") + part;
                    address.append(part); //添加
                    backTracking(addresses,address,k+1,s.substring(i+1));
                    address.delete(address.length()-part.length(),address.length()); //删除
                }
            }
        }
    }

131. 分割回文串

该题目是将整个字符串分割成 完全由回文字符串组成的list
与93复原IP地址的分割处理方式很像


public List<List<String>> partition(String s) {
        List<List<String>>lists = new ArrayList<>();
        backTracking(s,lists,new ArrayList<>());

        return lists;

    }

    private void backTracking(String s,List<List<String>>lists,List<String>list){
        if(s.length()==0){  //字符都被放置完了
            lists.add(new ArrayList<>(list));
            return;
        }

        for(int i=0;i<s.length();i++){
            String subStr = s.substring(0,i+1); //截取前多少个字符
            if(isPalindrome(subStr,0,i)){ //保证每一子段都是回文才可以继续
                list.add(subStr); 
                backTracking(s.substring(i+1),lists,list);
                list.remove(list.size()-1);
            }
        }
    }


    private boolean isPalindrome(String subStr,int start,int end){
        while(start<end){
            if(subStr.charAt(start++)!=subStr.charAt(end--)){
                return false;
            }
        }
        return true;
    }

棋谱填空

这种问题在于约束的构建

37. 解数独

class Solution {
    private boolean[][] lines = new boolean[9][9];
    private boolean[][] columns = new boolean[9][9];
    private boolean[][][] block = new boolean[3][3][9];
    private List<int[]> spaces = new ArrayList<>(); 

    private boolean valid = false; //标注有没有完成
    public void solveSudoku(char[][] board) {
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                if(board[i][j]=='.'){
                    spaces.add(new int[]{i,j});
                }else{
                    int digit = board[i][j] - '0';
                    lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=true;
                    //表示第i行            第j列               第[i/3][j/3]个九宫格       放置了数字digit
                }
            }
        }

        dfs(board,0);
    }


    private void dfs(char[][]board,int pos){
        if(pos==spaces.size()){
            valid = true;
            return;
        }
        int[] space = spaces.get(pos);
        int i = space[0], j = space[1];
        for(int digit=1;digit<=9&&!valid;digit++){  //看每个空白 是否能放下某个数
            if(!lines[i][digit-1]&&!columns[j][digit-1]&&!block[i/3][j/3][digit-1]){
                //都为false
                board[i][j] = (char)(digit+'0');
                lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=true;
                dfs(board,pos+1);
                lines[i][digit-1] = columns[j][digit-1] = block[i/3][j/3][digit-1]=false;
            }
        }
    }
}

51. N 皇后

和题37解数独一个想法 记录相关不可放的位置


class Solution {
    private char[][] nQueens;  //棋盘
    private boolean[] diagonals45Used; //记录45°主对角线
    private boolean[] diagonals135Used; 

    private boolean[] colums;

    private int n;

    public List<List<String>> solveNQueens(int n) {

        List<List<String>>lists = new ArrayList<>();

        this.n = n;
        nQueens = new char[n][n];
        for(int i=0;i<nQueens.length;i++){
            Arrays.fill(nQueens[i],'.');
        }
        
        diagonals45Used = new boolean[2*n-1]; //最多一行带一列,去掉一公共的
        diagonals135Used = new boolean[2*n-1];

        colums = new boolean[n];

        backTracking(lists,0);

        return lists;
    }

    private void backTracking(List<List<String>>lists,int r){
        if(r==n){
            List<String> list = new ArrayList<>();
            for(char[]rows:nQueens){
                list.add(new String(rows));
            }
            lists.add(list);
            return;
        }
        for(int c=0;c<n;c++){
            int index45Used =  r + c; //45°线上该值相等
            int index135Used = n - 1 -(r-c);  //135°线上该值相等

            if(diagonals45Used[index45Used]||diagonals135Used[index135Used]||colums[c]){
                continue;
            }
            diagonals45Used[index45Used]=diagonals135Used[index135Used]=colums[c] = true;
            nQueens[r][c] = 'Q';
            backTracking(lists,r+1);
            nQueens[r][c] = '.';
            diagonals45Used[index45Used]=diagonals135Used[index135Used]=colums[c] = false;
            
        }
    }
}

1424. 对角线遍历 II

45°

int length = 0;
        //根据对角线i+j唯一且相同,LinkedHashMap保持插入排序。效率是最优的
        Map<Integer,List<Integer>> map =new LinkedHashMap<>();
        for(int i = 0;i < nums.size();i++) {
            length += nums.get(i).size(); 
            for(int j = 0;j < nums.get(i).size();j++) {
                List<Integer> orDefault = map.getOrDefault(i + j, new ArrayList<>());
                orDefault.add(nums.get(i).get(j));
                map.putIfAbsent(i+j,orDefault);
            }
        }
        int[] result = new int[length];
        int index = 0;
        //遍历map,得到结果。
        for(int key : map.keySet()) { 
            List<Integer> list = map.get(key);
            for(int j = list.size() - 1;j >= 0;j--) { 
                result[index] = list.get(j);
                index++;
            }
        }
        return result;

数据结构

437. 路径总和 III

//主要采取了 横向遍历+ 单点处理

class Solution {
    private int pathSum = 0;  //记录所以结果

    public int pathSum(TreeNode root, int sum) {
        LinkedList<TreeNode> levelNodes = new LinkedList<>();
        if(root!=null){
            levelNodes.add(root);
        }

        while(!levelNodes.isEmpty()){
            int size = levelNodes.size();
            for(int i=0;i<size;i++){
                TreeNode node = levelNodes.remove();
                if(node.left!=null){
                    levelNodes.add(node.left);
                }
                if(node.right!=null){
                    levelNodes.add(node.right);
                }
                getSum(node,sum);
            }
        }
        return pathSum;
    }

    public void getSum(TreeNode root,int sum){
        if(root==null)return;

        if(root.val==sum)pathSum+=1;
        getSum(root.left,sum-root.val);
        getSum(root.right,sum-root.val);
    }
}

572. 另一个树的子树_

//这题和437思想类似
isSubtree负责宏观遍历s每一个节点,isSubtreeWithNode对每个节点进行微观细找

class Solution {
    public boolean isSubtree(TreeNode s, TreeNode t) {
        if(s==null)return false;

        return isSubtree(s.left,t) || isSubtree(s.right,t) || isSubtreeWithNode(s,t);
    }

    public boolean isSubtreeWithNode(TreeNode s, TreeNode t){
        if(s==null&&t==null)return true;
        if(s==null||t==null)return false;
        if(s.val!=t.val)return false;
        return isSubtreeWithNode(s.left,t.left) && isSubtreeWithNode(s.right,t.right);
    }
}

111. 二叉树的最小深度_

比最大深度要多考虑的就是,如果只有一个分支的话

public int minDepth(TreeNode root) {
        if(root==null)return 0;

        int l =  minDepth(root.left);
        int r = minDepth(root.right);
        if(l==0||r==0){
            return l+r+1; //咱得取有叶子节点的子树高度
        }

        return Math.min(l,r)+1;
    }

110. 平衡二叉树_

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

class Solution {
    public boolean isBalanced(TreeNode root) {
        return height(root)>=0;
    }

    private int height(TreeNode root){
        if(root==null)return 0;

        int heightL = height(root.left);
        int heightR = height(root.right);
        
        // if(Math.abs(heightL-heightR)>0)return -1;
        if(heightL==-1||heightR==-1||Math.abs(heightL-heightR)>1) return -1;  //这个-1会一直被传递上去
			 //是平衡节点则返回高度
        return Math.max(heightL,heightR)+1;
    } 

98. 验证二叉搜索树

//思路:利用中序遍历 输出值从小到大


class Solution {
    private long pre = Long.MIN_VALUE;
    public boolean isValidBST(TreeNode root) {
        if(root==null)return true;  //特殊情况:当前节点是null 

        if(!isValidBST(root.left)){ //如果产生false那就一直传递上去
            return false;
        }
				//这儿的root.val是中序值,而此时的pre经过root.left的更新
        if(root.val<=pre)return false; //产生false的出处

        pre = Math.max(pre,root.val); //更新当前节点的最小下限(不可达)

        return isValidBST(root.right);
    }
}

958.完全二叉树校验:和树的宽度解法很像

class Solution {
    public boolean isCompleteTree(TreeNode root) {
        List<ANode> nodes = new ArrayList();
        nodes.add(new ANode(root, 1));
        int i = 0;
        while (i < nodes.size()) {
            ANode anode = nodes.get(i++);
            if (anode.node != null) {
                nodes.add(new ANode(anode.node.left, anode.code * 2));
                nodes.add(new ANode(anode.node.right, anode.code * 2 + 1));
            }
        }

        return nodes.get(i-1).code == nodes.size();
    }
}

class ANode {  // Annotated Node
    TreeNode node;
    int code;
    ANode(TreeNode node, int code) {
        this.node = node;
        this.code = code;
    }
}

662. 二叉树最大宽度

public int widthOfBinaryTree(TreeNode root) {
        int res = 0;
        LinkedList<Node> levelNodes = new LinkedList<>();
        if(root!=null)levelNodes.add(new Node(root,1));

        while(!levelNodes.isEmpty()){
            res = Math.max(res,levelNodes.getLast().index-levelNodes.get(0).index+1);
            int size = levelNodes.size();
            for(int i = 0;i<size;i++){
                Node remove = levelNodes.remove();
                
                if(remove.node.left!=null){
                    Node left = new Node(remove.node.left,remove.index*2);
                    levelNodes.add(left);
                }
                if(remove.node.right!=null){
                    Node right = new Node(remove.node.right,remove.index*2+1);
                    levelNodes.add(right);
                }
            }   
        }
        return res;
    }
    class Node{
        TreeNode node;
        int index; //记录位置
        public Node(TreeNode node,int index){
            this.node = node;
            this.index = index;
        }
    }

337. 打家劫舍 III 间隔遍历

关注当前节点要什么就好。


public int rob(TreeNode root) {
        if(root==null)return 0;
        int val1= root.val;
        if(root.left!=null)val1+=rob(root.left.left)+rob(root.left.right);
        if(root.right!=null)val1+=rob(root.right.left)+rob(root.right.right);

        int val2 = rob(root.left)+rob(root.right);

        return Math.max(val1,val2);
    }

层次遍历


public List<Double> averageOfLevels(TreeNode root) {
        LinkedList<TreeNode> levelNodes = new LinkedList<>();
        List<Double> list = new ArrayList<>();
        if(root!=null)levelNodes.add(root);

        while(!levelNodes.isEmpty()){
            int size = levelNodes.size();
            double temp = 0;
            for(int i = 0;i<size;i++){
                TreeNode remove = levelNodes.remove();
                temp += remove.val;
                if(remove.left!=null){
                    levelNodes.add(remove.left);
                }
                if(remove.right!=null){
                    levelNodes.add(remove.right);
                }
            }
            list.add(temp / size);
        }

        return list;
    }

非递归实现二叉树的前序遍历

//这个的想法和递归出栈有些类似

public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> stack = new LinkedList<>();

        if(root!=null)stack.push(root);

        while(!stack.isEmpty()){
            TreeNode remove = stack.pop();  //root点肯定得先弹出来
            list.add(remove.val); //当前优先
            
            if(remove.right!=null)stack.push(remove.right);
            if(remove.left!=null)stack.push(remove.left); //其次左优先
        }
        return list;
    }

非递归实现二叉树的后序遍历

前序:root->left->right 后序:left->right->root
将前序改成root -> right -> left其reverse即为后序


public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> stack = new LinkedList<>();

        if(root!=null)stack.push(root);

        while(!stack.isEmpty()){
            TreeNode remove = stack.pop();
            list.add(remove.val);
            
            if(remove.left!=null)stack.push(remove.left); 
            if(remove.right!=null)stack.push(remove.right);
        }
        Collections.reverse(list);
        return list;
    }

非递归实现二叉树的中序遍历

public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        LinkedList<TreeNode> stack = new LinkedList<>();

        while(root!=null||!stack.isEmpty()){
            while(root!=null){ //利用循环下到节点的最左下角
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();
            list.add(root.val);
            root = root.right; //看看有没有右节点
        }
        return list;
    }

105. 从前序与中序遍历序列构造二叉树


```java
public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder == null || preorder.length == 0) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[0]);
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        stack.push(root);
        int inorderIndex = 0;
        for (int i = 1; i < preorder.length; i++) {
            int preorderVal = preorder[i];
            TreeNode node = stack.peek();
            if (node.val != inorder[inorderIndex]) { //不等于栈顶即还有左,继续入栈
                node.left = new TreeNode(preorderVal);
                stack.push(node.left);  //前序的顺序入栈左其出栈时和中序是一样的,不一样则遇见右
            } else {  //等于栈顶 即没有左,弹结束遇到右
                while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) {
                    node = stack.pop();
                    inorderIndex++;
                }
                node.right = new TreeNode(preorderVal);
                stack.push(node.right);
            }
        }
        return root;
    }

二叉树的z字形遍历 面试官说:不使用队列,想想其他数据结构

递归方式

    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        travel(root, res, 0);
        return res;
    }

    private void travel(TreeNode cur, List<List<Integer>> res, int level) {
        if (cur == null)
            return;
        //如果res.size() <= level说明下一层的集合还没创建,所以要先创建下一层的集合
        if (res.size() <= level) {
            List<Integer> newLevel = new LinkedList<>();
            res.add(newLevel);
        }
        //遍历到第几层我们就操作第几层的数据
        List<Integer> list = res.get(level);
        //这里默认根节点是第0层,偶数层相当于从左往右遍历,
        // 所以要添加到集合的末尾,如果是奇数层相当于从右往左遍历,
        // 要把数据添加到集合的开头
        if (level % 2 == 0)
            list.add(cur.val);
        else
            list.add(0, cur.val);
        //分别遍历左右两个子节点,到下一层了,所以层数要加1
        travel(cur.left, res, level + 1);
        travel(cur.right, res, level + 1);
    }

669. 修剪二叉搜索树

要利用函数的意思,再配合正向思考逻辑

//二叉搜索树   左小 右大 当前居中
public TreeNode trimBST(TreeNode root, int low, int high) {
        if(root==null)return null;
				//当前不满足范围要求,往里面看
        if(root.val>high)return trimBST(root.left,low,high); 
        if(root.val<low)return trimBST(root.right,low,high);
				//当前满足范围要求,往两边看
        root.left=trimBST(root.left,low,high);
        root.right=trimBST(root.right,low,high);

        return root;
    }

230. 二叉搜索树中第K小的元素

用中序遍历(非递归方式)

public int kthSmallest(TreeNode root, int k) {
        Stack<TreeNode>stack = new Stack<>();
        if(root==null)return 0;
        while(root!=null||!stack.empty()){
            while(root!=null){
                stack.push(root);
                root = root.left;
            }
            TreeNode remove = stack.pop();
            k --;
            if(k==0)return remove.val;
            root = remove.right;
        }
        return 0;
    }

//递归方式 为保持递归中序特性,成员变量持久记录更新替换最初的print(输出持久化)

private int cnt = 0;
private int val;

public int kthSmallest(TreeNode root, int k) {
    inOrder(root, k);
    return val;
}

private void inOrder(TreeNode node, int k) {
    if (node == null) return;
    inOrder(node.left, k);
    cnt++;
    if (cnt == k) {
        val = node.val;
        return;
    }
    inOrder(node.right, k);
}

538. 把二叉搜索树转换为累加树

二叉搜索树中序遍历时从小到大 但是吧本题得累加大的数
利用中序的倒序方式

private int sum = 0;
    public TreeNode convertBST(TreeNode root) {
        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);
    }

129.求根节点到叶节点数字之和

public int sumNumbers(TreeNode root) {
        return dfs(root, 0);
    }

    public int dfs(TreeNode root, int prevSum) {
        if (root == null) {
            return 0;
        }
        int sum = prevSum * 10 + root.val;
        if (root.left == null && root.right == null) {
            return sum;
        } else {
            return dfs(root.left, sum) + dfs(root.right, sum);
        }
    }

1530. 好叶子节点对的数量

后序的方式 先left right 再root

class Solution {
    private int ans;
    public int countPairs(TreeNode root, int distance) {
        dfs(root,distance);
        return ans;
    }

    private ArrayList<Integer> dfs(TreeNode node,int distance){
        if(node==null)return new ArrayList(); //不能return null要不空指针

        ArrayList<Integer> ret = new ArrayList();
        if(node.left==null&&node.right==null){
            ret.add(1);
            return ret;
        } 

        ArrayList<Integer>left = dfs(node.left,distance);
        for(int n:left){
            if(++n>distance)continue;  //当前 为左右孩子的路径+1
            ret.add(n);
        }

        ArrayList<Integer>right = dfs(node.right,distance);
        for(int n:right){
            if(++n>distance)continue;
            ret.add(n);
        }

        for(int l:left){ //当前的 只看左与右的匹配
            for(int r:right){
                ans +=(l+r<=distance?1:0);
            }
        }
        return ret;
    }
}

687. 最长同值路径

最大路径和问题


private int pathNum = 0;
    public int longestUnivaluePath(TreeNode root) {
        partPath(root);
        return pathNum;
    }

    private int partPath(TreeNode root){
        if(root==null)return 0;

        int l = partPath(root.left);
        int r = partPath(root.right);

        int pathL = root.left!=null&&root.left.val==root.val?l+1:0;
        int pathR = root.right!=null&&root.right.val==root.val?r+1:0;

        pathNum = Math.max(pathNum,pathL+pathR);

        return Math.max(pathL,pathR);
    }

124. 二叉树中的最大路径和

class Solution {
    private int pathSum = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
         maxPathRecord(root);
         return pathSum;
    }
    private int maxPathRecord(TreeNode root){
        if(root==null)return 0;

        int left = Math.max(maxPathRecord(root.left),0);
        int right =  Math.max(maxPathRecord(root.right),0);

        int newPathSum= root.val + left + right; //当前点的路径和
        pathSum = Math.max(newPathSum,pathSum); //更新最大路径
        return root.val+Math.max(left,right); //返回当前节点所能提供的最大贡献度
    }
}

235. 二叉搜索树的最近公共祖先_

对比236


public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
        if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p,q);
				//在不同边直接返回
				return root;
    }

236. 二叉树的最近公共祖先

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        //特殊情况
        if(root==null||root==p||root==q)return root;

        //开始往下找
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        
        if(leftNode!=null&&rightNode!=null) return root; //(1)在当前的子树下

        return leftNode!=null?leftNode:rightNode; //将非null的保留 随出栈肯定会满足(1)

    }
}

109. 有序链表转换二叉搜索树

利用快慢指针找链表中点,然后分治

public TreeNode sortedListToBST(ListNode head) {
        //1. 特例处理
        if (head == null){
            return null;
        }else if(head.next == null){
            return new TreeNode(head.val);
        }
        //2. 利用快慢指针找链表中间节点
        ListNode slow = head, fast = head;
        ListNode pre = null;
        while( fast != null && fast.next != null){
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
        }
        //3. 创建树的根节点,并断开相应连接
        TreeNode root = new TreeNode(slow.val);
        pre.next = null;

        //4. 递归链表中间节点左右两边的子链表
        root.left = sortedListToBST(head);
        root.right = sortedListToBST(slow.next);
        return root;
    }

653. 两数之和 IV - 输入 BST_

中序的结果是有顺序的 然后从两边各自开始微调


public boolean findTarget(TreeNode root, int k) {
        List<Integer> list = new ArrayList<>();
        inorder(root,list);
        int i = 0, j = list.size()-1;

        while(i<j){
            
            int sum = list.get(i)+list.get(j);
            if(sum==k)return true;
            if(sum<k)i++;
            else j--;
        }
        return false;
        
    }
    private void inorder(TreeNode root,List<Integer>list){
        if(root==null)return;
        inorder(root.left,list);
        list.add(root.val);
        inorder(root.right,list);
    }


208. 实现 Trie (前缀树)

字典树:用于存储查找有公告前缀的字符串
每个字符所在位置 都是一个26位的数组,前一个字符指向后一个字符所在的数组

路径上每个分支都代表一个字符

class Trie {
    private class Node{
        Node[] childs = new Node[26];
        boolean isLeaf;
    }

    private Node root = new Node();

    /** Initialize your data structure here. */
    public Trie() {
        
    }
    
    /** Inserts a word into the trie. */
    public void insert(String word) {
        insert(word,root);
    }

    private void insert(String word,Node node){ //在数组中找位置
        if(node==null)return;

        if(word.length()==0){  //插完事了
            node.isLeaf =true; //给个标致 为true的构成一个完整字符串
            return;
        }

        int index = indexForChar(word.charAt(0));
        
        if(node.childs[index]==null){  //要插入的位置没有元素
            node.childs[index] = new Node(); //插入 即 该位置指向下一个字符所在数组
        }

        insert(word.substring(1),node.childs[index]);
    }

    private int indexForChar(char c){ //位置计算
        return c-'a';
    }
    
    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        return search(word,root);
    }

    private boolean search(String word,Node node){
        if(node==null)return false;
        if(word.length()==0){
            return node.isLeaf; //有两种可能 true就是当初有这个字符串
        }
        int index = indexForChar(word.charAt(0));
        return search(word.substring(1),node.childs[index]);
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        return startsWith(prefix,root);
    }

    public boolean startsWith(String prefix,Node node){
        if(node==null)return false;
        if(prefix.length()==0){ //与找字符串唯一区别就在这
            return true;
        }
        int index = indexForChar(prefix.charAt(0));
        return startsWith(prefix.substring(1),node.childs[index]);

    }
}

677. 键值映射

class MapSum {

    /** Initialize your data structure here. */
    public MapSum() {

    }

    private class Node{
        Node[] childs = new Node[26];
        int val;
    }

    private Node root = new Node();


    
    public void insert(String key, int val) {
        insert(key,val,root);
    }

    public void insert(String key,int val,Node node){
        if(node==null)return;

        if(key.length()==0){
            node.val = val;
            return;
        }

        int index = indexForChar(key.charAt(0));
        if(node.childs[index]==null){
            node.childs[index] = new Node();
        }

        insert(key.substring(1),val,node.childs[index]);
    }

    private int indexForChar(char c){
        return c-'a';
    }
    
    public int sum(String prefix) {
        return sum(prefix,root);
    }

    public int sum(String prefix, Node node){
        if(node==null)return 0;
        
        if(prefix.length()!=0){ //消耗完prefix
            int index = indexForChar(prefix.charAt(0));
            return sum(prefix.substring(1),node.childs[index]); //消耗完prefix的那个return最先返回
        }

        int sum = node.val; //被加的值
        for(Node child:node.childs){ //对每个节点进行
            if(node!=null){
                sum+= sum(prefix,child);
            }
        }
        return sum;
    }
}

440. 字典序的第K小数字

十叉树

public int findKthNumber(int n, int k) {
        long cur = 1;
        k -= 1;
        while(k>0){
            long steps = getCount(cur,n);
            if(k>=steps){
                k -= steps;
                cur += 1; //去后一个 兄弟
            }else{
                k -= 1;
                cur *= 10; //去下一层 子
            }
        }
        return (int)cur;    
    }
    private int getCount(long cur,int n){ //前后两兄弟的序号差值
        long nextCur = cur +1;
        long steps = 0;
        while(cur<=n){
            steps += Math.min(nextCur-cur,n-cur+1);
            nextCur *= 10;
            cur *= 10;
        }
        return (int)steps;
    }

字符串

647 回文字符串

思路:字符串长度分两类:奇、偶 以每个初始为出发点向两边延展寻找更多可能


class Solution {
    private int cnt;
    public int countSubstrings(String s) {
        for(int i=0;i<s.length();i++){
            extendString(s,i,i); //奇数长度
            extendString(s,i,i+1); //偶数长度
        }
        return cnt;
    }
    private void extendString(String s,int start, int end){
        while(start>=0&&end<s.length()&&s.charAt(start)==s.charAt(end)){  //向两边延展
            cnt ++;
            start --;
            end++;
        }
    }
}

205. 同构字符串_

思路:字符之间存在映射,所以考虑HashMap


public boolean isIsomorphic(String s, String t) {
        HashMap<Character,Character> s2t = new HashMap<>();
        HashMap<Character,Character> t2s = new HashMap<>();

        for(int i=0;i<s.length();i++){
            char x = s.charAt(i);
            char y = t.charAt(i);
            if(s2t.containsKey(x)&&s2t.get(x)!=y||t2s.containsKey(y)&&t2s.get(y)!=x){
                return false;
            }
            s2t.put(x,y);
            t2s.put(y,x);
        }
        return true;
    }

9. 回文数_

字符串解法

public boolean isPalindrome(int x) {
        if(x<0)return false;
        String str = String.valueOf(x);
        char[] chars = str.toCharArray();
        int end = str.length()-1;
        int start = 0;
        while(start<end){
            if(chars[start]!=chars[end])return false;
            start++;
            end --;
        }
        return true;
    }

整数解法

public boolean isPalindrome(int x) {
    if (x == 0) {
        return true;
    }
    if (x < 0 || x % 10 == 0) { //负数或者个位为0的正整数
        return false;
    }
    int right = 0;
    while (x > right) { //若剩余的高位还高
        right = right * 10 + x % 10; 
        x /= 10;
    }
    return x == right || x == right / 10;
}

696计数二进制_

public int countBinarySubstrings(String s) {
        int ptr = 0,count=0,last=0,cnt=0;
        while(ptr<s.length()){
            char c = s.charAt(ptr);
            count = 0;
            while(ptr<s.length()&&s.charAt(ptr)==c){ //寻找当前字符串的连续数
                ++ptr;
                ++count;
            }
            cnt += Math.min(count,last);  //取之前字符串连续数与当期连续数的小值
            last = count;
        }
        return cnt;
    }

567. 字符串的排列

s1 的排列之一是 s2 的 子串 。

public boolean checkInclusion(String s1, String s2) {
        int m = s1.length(), n = s2.length();
        if(m>n)return false;
        int[] cnt = new int[26];
        for(char c:s1.toCharArray()){
            --cnt[c-'a'];
        }
        int left = 0;
        for(int right=0;right<n;right++){
            int x = s2.charAt(right) - 'a';
            ++cnt[x];
            while(cnt[x]>0){ //目标抵消就好 即cnt[x]=0
                --cnt[s2.charAt(left)-'a']; //左边收窄
                ++left;
            }
            if(right-left+1==m){
                return true;
            }
        }
        return false;
    }

76. 最小覆盖子串

参考LC567. 字符串的排列 但最后一个用例(巨长的字符串)没通过

    public String minWindow(String s, String t) {
        int m = s.length(), n = t.length();

        String ans = "";
        int index = m+1;
        if(m<n) return ans;
        
        int[] cnt = new int[128];
        for(char c:t.toCharArray()){
            --cnt[c];
        }

        int left = 0;
        for(int right=0;right<m;right++){
            ++cnt[s.charAt(right)];
            while(left<=right&&cnt[s.charAt(left)]>0){
                --cnt[s.charAt(left)];
                left++;
            }
            if(check(cnt,t)){ //满足要求的
                if(index>right-left+1){
                    ans = s.substring(left,right+1);
                    index = right-left+1;
                }
            }
        }
        return ans;

    }
    private boolean check(int[]cnt,String t){
        for(char c:t.toCharArray()){
            if(cnt[c]<0)return false;
        }
        return true;
    }

402. 移掉 K 位数字

参考link

public String removeKdigits(String num, int k) {
        LinkedList<Integer>stack = new LinkedList();

        for(char c:num.toCharArray()){
            int x = c - '0';
            while(!stack.isEmpty()&&x<stack.peek()&&k-->0){
                stack.pop();
            }
            if(stack.isEmpty()&&x==0){ //栈为空,'0'就不放了
                // k--;
                continue;
            }
            stack.push(x);
        }

        while(!stack.isEmpty()&&k-->0){ //还没有删除足够元素 从后面出栈删除大元素
            stack.pop();
        }
        if(stack.isEmpty())return "0";

        StringBuilder sb = new StringBuilder();
        while(!stack.isEmpty()){
            sb.insert(0,stack.pop());
        }
        return sb.toString();  
    }

1405. 最长快乐字符串

public String longestDiverseString(int a, int b, int c) {
        MyChar[]myChars = new MyChar[]{
            new MyChar('a',a),
            new MyChar('b',b),
            new MyChar('c',c)
        };

        StringBuilder sb = new StringBuilder();
        while(true){
            Arrays.sort(myChars);
            int size = sb.length();
            //放完两个字符还是最多
            if(size>=2&&sb.charAt(size-1)==myChars[2].ch&&sb.charAt(size-2)==myChars[2].ch){
                if(myChars[1].cnt-->0){
                    sb.append(myChars[1].ch); //放次多元素
                }else{
                    break;
                }
                
            }else{
                if(myChars[2].cnt-->0){
                    sb.append(myChars[2].ch);
                }else{
                    break;
                }
                
            }
        }
        return sb.toString();

    }
    private class MyChar implements Comparable{
        char ch;
        int cnt;

        public MyChar(char ch,int cnt){
            this.ch = ch;
            this.cnt = cnt;
        }

        @Override
        public int compareTo(Object o){
            MyChar other = (MyChar)o;
            return this.cnt - other.cnt; //升序
        }
    }

小点补充

1.利用数组当前字符的个数

        for(char c : s.toCharArray()){
            cnts[c-'A']++;
        }

链表

160. 相交链表_

每个节点只有一个后继节点,即若有相交节点的话,则该节点后 A B 都一样
交换访问的方式,规避一致链前长度不一样的问题

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode l1 = headA;
        ListNode l2 = headB;
        while(l1!=l2){
            l1 = (l1==null)?headB:l1.next;
            l2 = (l2==null)?headA:l2.next; 
        }
        return l1;
    }

NC3 链表中环的入口结点

方法一:使用一个HashSet,遍历链表,当出现重复节点即有环,立即返回

import java.util.*;
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null)
            return pHead;
        Set<ListNode> set = new HashSet<>();

        while(pHead != null){
            if(set.contains(pHead)){
                return pHead;
            }
            set.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
}

方法二:快慢指针
在这里插入图片描述

分析:根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有
a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)

有了 a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上n−1 圈的环长,恰好等于从链表头部到入环点的距离。

因此,当发现 slow 与 fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        if(pHead==null)return null;
        ListNode slow = pHead,fast = pHead;
        while(fast!=null){
            slow = slow.next;
            if(fast.next!=null){
                fast = fast.next.next;
            }else{
                return null;
            }
            if(fast==slow){ //相遇啦
                ListNode ptr = pHead;
                while(ptr!=slow){
                    ptr=ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }

206. 反转链表_

//头插法

public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode cur = head;
        while(cur!=null){
            ListNode temp = cur.next;
            cur.next = prev;
            prev = cur;

            cur = temp;
        }
        return prev;
    }

92. 反转链表 II

public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;

        int i = 0;
        ListNode newHead=pre,newTail = pre;
        while(i<right){ //先找到要翻转的区间
            newTail = newTail.next;
            if(i<left){
                pre = newHead;
                newHead = newHead.next;
            }
            i++;
        }
        ListNode next = newTail.next;
        newTail.next = null;

        ListNode[]reverse = reverse(newHead);
        pre.next = reverse[0];
        reverse[1].next = next;

        return dummy.next;
    }
    private ListNode[] reverse(ListNode head){
        ListNode prev = null;
        ListNode cur = head;
        while(cur!=null){
            ListNode temp = cur.next;
            cur.next = prev;
            prev = cur;

            cur = temp;
        }
        return new ListNode[]{prev,head};
    } 

25. K 个一组翻转链表

public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode pre = dummy, end = dummy;
        while(end.next!=null){
            for(int i=0;i<k&&end!=null;i++)end = end.next;
            if(end==null)break;
            ListNode start = pre.next;
            ListNode next = end.next;
            end.next = null; //切断
            pre.next = reverse(start);
            start.next = next;
            pre = start;

            end = pre;
        }
        return dummy.next;
    }
    private ListNode reverse(ListNode head) { //头插法
        ListNode pre = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = pre;
            pre = curr;
            curr = next;
        }
        return pre;
    }

61. 旋转链表

将链表每个节点向右移动 k 个位置。

if (head == null || head.next == null) {
            return head;
        }
        int len = 1, index;
        ListNode temp = head, newHead;
        while (temp.next != null) { //计算链表长度len
            len++;
            temp = temp.next;
        }
        temp.next = head; //将链表连接成循环链表
        k %= len; //旋转链表每len次循环一次,因此计算k对len的取余,避免重复操作
        index = len - k; //找到要断开循环链表的节点纪录链表新的头结点
        while (index-- > 0) {
            temp = temp.next;
        }
        newHead = temp.next;
        temp.next = null; //断开循环链表
        return newHead;

19.删除链表的倒数第 n 个节点

用保持间隔的快慢指针


 public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast = head;
        while(n-->0){ //保持间隔(索引差值)
            fast = fast.next;
        }
        if(fast==null)return head.next;
        ListNode slow = head;
        while(fast.next!=null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;

        return head;
    }

82. 删除排序链表中的重复元素 II

public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        while(pre.next!=null&&pre.next.next!=null){
            if(pre.next.val==pre.next.next.val){
                int x = pre.next.val;
                while(pre.next!=null&&pre.next.val==x){
                    pre.next = pre.next.next;
                }
            }else{
                pre = pre.next;
            }
        }
        return dummy.next;
        
    }

24. 两两交换链表中的节点

把握好pre


public ListNode swapPairs(ListNode head) {
        ListNode node = new ListNode(-1);
        node.next = head;
        ListNode pre = node; //

        while(pre.next!=null&&pre.next.next!=null){
            ListNode l1 = pre.next;
            ListNode l2 = pre.next.next;
            pre.next = l2;

            ListNode next = l2.next;
            l2.next = l1;

            l1.next = next;
            pre = l1;
        }
        return node.next;
    }

148. 排序链表归并排序(递归)

public ListNode sortList(ListNode head) {
        return sortList(head,null);
    }
    private ListNode sortList(ListNode head,ListNode tail){
        if(head==null)return head;

        if(head.next==tail){
            head.next = null; //这样处理源于拆分的时候都包含了mid节点
            return head;
        }
        ListNode slow = head, fast = head;
        while(fast!=tail){
            slow = slow.next;
            fast = fast.next;
            if(fast!=tail){
                fast = fast.next;
            }
        }
        ListNode mid = slow;
        ListNode list1 = sortList(head,mid);
        ListNode list2 = sortList(mid,tail);
        return merge(list1,list2);
    }
    private ListNode merge(ListNode headA,ListNode headB){
        ListNode dummyHead = new ListNode(-1);
        ListNode temp = dummyHead,temp1 = headA,temp2 = headB;
        while(temp1!=null&&temp2!=null){
            if(temp1.val<=temp2.val){
                temp.next = temp1;
                temp = temp.next;
                temp1 = temp1.next;
            }else{
                temp.next = temp2;
                temp = temp.next;
                temp2 = temp2.next;
            }
        }
        temp.next = temp1==null?temp2:temp1;
        return dummyHead.next;
    }

插入排序

public ListNode sortList(ListNode head) {
        if (head == null) {
            return head;
        }
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode lastSorted = head, curr = head.next;
        while (curr != null) {
            if (lastSorted.val <= curr.val) {
                lastSorted = lastSorted.next;
            } else {
                ListNode prev = dummyHead;
                while (prev.next.val <= curr.val) {
                    prev = prev.next;
                }
                lastSorted.next = curr.next;
                curr.next = prev.next;
                prev.next = curr;
            }
            curr = lastSorted.next;
        }
        return dummyHead.next;
    }

23. 合并K个升序链表

最小堆

public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null||lists.length==0)return null;
        ListNode preHead = new ListNode(-1),pre = preHead;
        int k = lists.length;

        PriorityQueue<ListNode>minHeap = new PriorityQueue<>(k,(a,b)->a.val-b.val);
        for(ListNode node:lists){
            if(node!=null){
                minHeap.add(node);
            }
        }
        while(!minHeap.isEmpty()){
            ListNode removeNode = minHeap.remove();
            if(removeNode.next!=null){
                minHeap.add(removeNode.next);
            }
            pre.next = removeNode;
            pre = pre.next;
        }

        return preHead.next;
    }

简单版本:21. 合并两个有序链表

143. 重排链表

public void reorderList(ListNode head) {
        if(head==null)return;
        ListNode mid = midNode(head);
        ListNode l2 = mid.next;
        mid.next = null;
        l2 = reverse(l2);
        merge(head,l2);
    }
    private ListNode midNode(ListNode head){ //取中
        ListNode slow = head,fast = head;
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }
    private ListNode reverse(ListNode head){ //翻转
        ListNode prev = null;
        ListNode cur = head;
        while(cur!=null){
            ListNode temp = cur.next;
            cur.next = prev;
            prev = cur;

            cur = temp;
        }
        return prev;
    }
    private void merge(ListNode l1,ListNode l2){
        while(l1!=null&&l2!=null){
            ListNode l1_temp = l1.next;
            l1.next = l2;
            
            ListNode l2_temp = l2.next;
            l2.next = l1_temp;

            l1 = l1_temp;
            l2 = l2_temp;
        }
    }

445. 两数相加 II

不要用Stack(底层是用Vector实现的)执行击败41% 内存击败37%
改用LinkedList 81% 61%

这题高位在首节点,所以需要用栈,先低位出栈运算


public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        LinkedList<Integer>stack1 = buildStack(l1);
        LinkedList<Integer>stack2 = buildStack(l2);
        int carry = 0;
        ListNode head = new ListNode(-1);
        while(!stack1.isEmpty() ||!stack2.isEmpty()||carry!=0){
            int val1 = stack1.isEmpty()?0:stack1.pop();
            int val2 = stack2.isEmpty()?0:stack2.pop();

            //头插法
            ListNode node = new ListNode((val1+val2+carry)%10);
            node.next = head.next;
            head.next = node;

            carry = (val1+val2+carry)/10;
        }
        return head.next;
    }

    private LinkedList<Integer> buildStack(ListNode l){
        LinkedList<Integer> stack = new LinkedList<>();
        while(l!=null){
            stack.push(l.val);
            l = l.next;
        }
        return stack;
    }

234. 回文链表_

快慢指针 2倍速切开

public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;
        ListNode slow = head, fast = head.next;
        while(fast!=null&&fast.next!=null){
            slow = slow.next;
            fast = fast.next.next; //2倍速
        }
        if(fast!=null) slow = slow.next; //奇数

        cut(head,slow);
        return isEqual(head,reverse(slow));
    }
    private void cut(ListNode head,ListNode cutNode){
        while(head.next!=cutNode){
            head = head.next;
        }
        head.next = null; //切断联系
    }
	
	//头插法翻转链表
    private ListNode reverse(ListNode node){
        ListNode preHead = new ListNode(-1);
        while(node!=null){
            ListNode temp = node.next;
            node.next = preHead.next;
            preHead.next = node;
            node = temp;
        }
        return preHead.next;
    }

    private boolean isEqual(ListNode l1, ListNode l2){
        while(l1!=null&&l2!=null){
            if(l1.val!=l2.val)return false;
            l1 = l1.next;
            l2 = l2.next;
        }
        return true;
    }


725. 分隔链表

public ListNode[] splitListToParts(ListNode root, int k) {
        int N = 0;
        ListNode cur = root;
        while(cur!=null){  //计数
            N ++;
            cur = cur.next;
        }
        int mode = N % k;
        int size = N / k;
        
        ListNode[] ret = new ListNode[k];
        cur = root;
        for(int i=0;cur!=null&&i<k;i++){
            ret[i] = cur;
            int curSize = size + (mode-->0?1:0);
            for(int j=0;j<curSize-1;j++){
                cur = cur.next;
            }
            ListNode temp = cur.next;
            cur.next = null; //斩断
            cur = temp;
        }
        return ret;
    }

328. 奇偶链表

   public ListNode oddEvenList(ListNode head) {
        if(head==null)return head;

        ListNode odd = head, even = head.next, evenHead = even;
        while(even!=null&&even.next!=null){ //even是偶数
            odd.next = odd.next.next; //连上下一个奇位置
            odd = odd.next;   //走到下一个奇位置(非空)

            even.next = even.next.next;
            even = even.next;        
        }
        odd.next = evenHead;
        
        return head;
    }

排序奇升偶降链表

在这里插入图片描述

栈和队列

739. 每日温度

没有大的就先将索引放在栈中(后进栈一定比栈中现存的值小), 遇到大的就将之前保存的较小的都出栈

    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] ret = new int[n];
        LinkedList<Integer> stack = new LinkedList<>();
        for(int curIndex = 0;curIndex<n;curIndex++){
            while(!stack.isEmpty() && temperatures[curIndex]>temperatures[stack.peek()]){
                int preIndex = stack.pop();
                ret[preIndex] = curIndex - preIndex;
            }
            stack.push(curIndex);
        }
        return ret;
    }

503. 下一个更大元素 II

public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] ret = new int[n];
        Arrays.fill(ret,-1);
        LinkedList<Integer>stack = new LinkedList<>();
        for(int i=0;i<n*2;i++){
            int num = nums[i%n];
            while(!stack.isEmpty() && num > nums[stack.peek()]){
                ret[stack.pop()] = num;
            }
            if(i<n){
                stack.push(i);
            }
        }
        return ret;
    }

394. 字符串解码

public String decodeString(String s) {
        StringBuilder res = new StringBuilder();
        int multi = 0;
        LinkedList<Integer> stack_multi = new LinkedList<>();
        LinkedList<String> stack_res = new LinkedList<>();
        for(Character c : s.toCharArray()) {
            if(c == '[') {
                stack_multi.addLast(multi);
                stack_res.addLast(res.toString());
							  //清零处理
                multi = 0;
                res = new StringBuilder();
            }
            else if(c == ']') {
                StringBuilder tmp = new StringBuilder();
                int cur_multi = stack_multi.removeLast();
                for(int i = 0; i < cur_multi; i++) tmp.append(res);
                res = new StringBuilder(stack_res.removeLast() + tmp);//将字符串与内层结果拼接
            }
            else if(c >= '0' && c <= '9') multi = multi * 10 + Integer.parseInt(c + ""); //计数
            else res.append(c);
        }
        return res.toString();
    }


计算器

227. 基本计算器 II

只有±*/吴3括号

public int calculate(String s) {
        //P108中缀表达式转后缀表达式 然后计算
        LinkedList<Character> operator_stack = new LinkedList();//操作符栈
        LinkedList<Integer> number_stack = new LinkedList(); //操作数栈
        Set<Character>signs = new HashSet(Arrays.asList('+','-','*','/'));
        int n = s.length();
        int idx = 0;
        while(idx<n){
            if(Character.isDigit(s.charAt(idx))){
                int num = s.charAt(idx++)-'0';
                while(idx<n&&Character.isDigit(s.charAt(idx))){
                    num = num*10 + s.charAt(idx)-'0';
                    ++idx;
                }
                number_stack.push(num); //后缀表达式 遇到数字直接入栈
            }else if(signs.contains(s.charAt(idx))){
                while(!number_stack.isEmpty()&&!operator_stack.isEmpty()&&priority(operator_stack.peek(),s.charAt(idx))){//当前操作符优先级不高于栈顶               
                    calculate(operator_stack,number_stack);//先出栈计算
                }
                operator_stack.push(s.charAt(idx)); //再把当前运算符放到操作符栈中去
                ++idx;
            }else if(s.charAt(idx)==' ')++idx;
            
        }
        while(!operator_stack.isEmpty())calculate(operator_stack,number_stack);
        return number_stack.peek();
    }
    private void calculate(LinkedList<Character> operator_stack,LinkedList<Integer> number_stack){
        // 拿出来操作数栈中顶层的两个数,再把操作符栈中顶层的符号取出来,进行计算后放到操作数栈中
        char operator = operator_stack.pop();
        int num1 = number_stack.pop(),num2 = number_stack.pop();
        if(operator=='+')number_stack.push(num2+num1);
        else if(operator=='-')number_stack.push(num2-num1);
        else if(operator=='*')number_stack.push(num2*num1);
        else number_stack.push(num2/num1);
    }
    private boolean priority(char top,char curr){ //不比栈顶优先级高方为true
        if(curr==top)return true;
        else if(top=='*'||top=='/')return true;
        else return (top=='+'||top=='-')&&(curr=='+'||curr=='-');
    }

哈希表

1. 两数之和_

用空间换时间

    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer,Integer>indexForNum = new HashMap();
        for(int i=0;i<nums.length;i++){
            if(indexForNum.containsKey(target-nums[i])){
                return new int[]{indexForNum.get(target-nums[i]),i};
            }else{
                indexForNum.put(nums[i],i);  //值-索引
            }
        }
        return null;
    }

138. 复制带随机指针的链表

    Map<Node, Node> cachedNode = new HashMap<Node, Node>();
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (!cachedNode.containsKey(head)) {
            Node headNew = new Node(head.val);
            cachedNode.put(head, headNew);
            //先递归完所有的next   防止随机指针指向的节点不存在
            headNew.next = copyRandomList(head.next);
            headNew.random = copyRandomList(head.random);
        }
        return cachedNode.get(head);
    }

128. 最长连续序列

public int longestConsecutive(int[] nums) {
        Map<Integer,Integer>map = new HashMap();
        for(int num:nums){
            map.put(num,1);
        }
        for(int num:nums){
            forward(map,num);
        }
        return maxCount(map,nums);
    }
    private int forward(Map<Integer,Integer>map,int num){
        if(!map.containsKey(num)){
            return 0;
        }
        int cnt = map.get(num);
        if(cnt>1){  //指第一轮遍历完事后已经存储过,就不用再次遍历了
            return cnt;
        }
        cnt = forward(map,num+1)+1; //求取当前元素可达的最大连续长度
        map.put(num,cnt);

        return cnt;
    }
    private int maxCount(Map<Integer,Integer>map,int[] nums){
        int max =0;
        for(int num:nums){
            max = Math.max(max,map.get(num));
        }
        return max;
    }

//这个方法相对方法一消耗高一点
 public int longestConsecutive(int[] nums) {
        Set<Integer>set = new HashSet();
        for(int num:nums){
            set.add(num);
        }
        int res = 0;
        for(int x:nums){
            if(!set.contains(x-1)){
                int y = x;
                while(set.contains(y+1)){
                    y++;
                }
                res = Math.max(res,y-x+1);
            }

        }
        return res;
    }

数组与矩阵

565. 数组嵌套

public int arrayNesting(int[] nums) {
        int max = 0;
        for(int i=0;i<nums.length;i++){
            int cnt = 0;
            for(int j=i;nums[j]!=-1;){
                int t = nums[j];
                cnt ++;
                nums[j] = -1; // 标记该位置已经被访问
                j = t;
            }
            max = Math.max(max,cnt);
        }
        return max;
    }

162. 寻找峰值

线性扫描

    public int findPeakElement(int[] nums) {
        for(int i=0;i<nums.length-1;i++){
            if(nums[i]>nums[i+1])return i; //题目只需要返回一个,此刻nums[i-1] < nums[i],要不直接return了
				}
        return nums.length -1;
    }

31. 下一个排列

class Solution {
    public void nextPermutation(int[] nums) {
        int n = nums.length,i = n-2;
        while(i>=0&&nums[i]>=nums[i+1]){//从后面开始查找,不满足升序的元素
            i--;
        }
        
        if(i>=0){
            int j  = n -1;
            while(j>i&&nums[j]<=nums[i]){ //找出一个稍大的交换
                j--;
            }
            swap(nums,i,j);
        }
        reverse(nums,i+1);

    }
    private void swap(int[]nums,int i,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
    private void reverse(int[]nums,int start){
        int end = nums.length-1;
        while(end>start){
            swap(nums,start,end);
            end--;
            start++;
        }
    }
}

类似题目 556. 下一个更大元素 III

53. 最大子序和_

public int maxSubArray(int[] nums) {
        if(nums.length==0)return 0;
        int preSum = nums[0];
        int maxSum = preSum;

        for(int i=1;i<nums.length;i++){
					//一旦preSum不大于0,前面的那些累积就不需要了
            preSum = preSum>0?preSum+nums[i]:nums[i]; 
            maxSum = Math.max(maxSum,preSum);
        }
        return maxSum;
        
    }

862. 和至少为 K 的最短子数组

前缀数组 303. 区域和检索 - 数组不可变

放栈有点像 739. 每日温度

public int shortestSubarray(int[] nums, int k) {
        int n = nums.length;
        int[]sums = new int[n+1];

        for(int i=0;i<n;i++)sums[i+1]=sums[i]+nums[i]; //构建前缀数组

        LinkedList<Integer>deque = new LinkedList();  //ps 栈是在双端队列头部进行 push and pop
        int minLen = Integer.MAX_VALUE;
        for(int i=0;i<=n;i++){
            while(!deque.isEmpty()&&sums[i]<=sums[deque.peekLast()])deque.pollLast();//我若比你小你根本没有优势
            while(!deque.isEmpty()&&sums[i]-sums[deque.peekFirst()]>=k){
                minLen = Math.min(minLen,i-deque.pollFirst());
            }
            deque.offer(i);
        }
        return minLen==Integer.MAX_VALUE?-1:minLen;
    }

209. 长度最小的子数组:和大于target的连续子序列的最小长度。

滑动窗口

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int n = nums.length;
        if (n == 0) {
            return 0;
        }
        int ans = Integer.MAX_VALUE;
        int start = 0, end = 0;
        int sum = 0;
        while (end < n) {
            sum += nums[end];
            while (sum >= s) {
                ans = Math.min(ans, end - start + 1);
                sum -= nums[start];
                start++;
            }
            end++;
        }
        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

一个无序数组,找到一个数,左边都比他小,右边都比他大

正遍历当前最大 标记
逆遍历当前最小 标记

	public int deal(int[]arr){
		int[] mos = new int[arr.length];
		findMiddle(arr,mos);
		for (int i = 0; i < arr.length; i++) {
            if (mos[i] == 2) {
                return arr[i];
            }
        }
	}

	private void findMiddle(int[] arr,int[] mos) {
        if (null == arr || arr.length <= 2) {
            return;
        }
        int max = 0;
        int min = arr.length-1;
        for (int i = 0; i < arr.length; i++) {
            max=arr[max]<=arr[i]?i:max;
            if (max == i) {
                mos[i]++;
            }
        }
        for (int i = arr.length-1; i >=0; i--) {
            min=arr[min]>=arr[i]?i:min;
            if (min == i) {
                mos[i]++;
            }
        }
    }

421. 数组中两个数的最大异或值

参考link

public class Solution {

    // 先确定高位,再确定低位(有点贪心算法的意思),才能保证这道题的最大性质
    // 一位接着一位去确定这个数位的大小
    // 利用性质: a ^ b = c ,则 a ^ c = b,且 b ^ c = a

    public int findMaximumXOR(int[] nums) {
        int res = 0;
        int mask = 0;
        for (int i = 30; i >= 0; i--) {
            // 注意点1:注意保留前缀的方法,mask 是这样得来的
            // 用异或也是可以的 mask = mask ^ (1 << i);
            mask = mask | (1 << i);

            // System.out.println(Integer.toBinaryString(mask));
            Set<Integer> set = new HashSet<>();
            for (int num : nums) {
                // 注意点2:这里使用 & ,保留前缀的意思(从高位到低位)
                set.add(num & mask);
            }

            // 这里先假定第 n 位为 1 ,前 n-1 位 res 为之前迭代求得
            int temp = res | (1 << i);
            for (Integer prefix : set) {
                if (set.contains(prefix ^ temp)) { //包含的话才说明temp可获取到,疑惑性质决定
                    res = temp;
                    break;
                }
            }
        }
        return res;
    }
}

求区间最小数x区间和的最大值

X= min(subArray)*sum(subArray)

前缀和、单调栈

	for(int i = 0; i < n; i ++) {
    	while(!stack.isEmpty() && a[i] <= a[stack.peek()]) {//我若比你小,下个区间就由我来了
    	  	int peak = a[stack.peek()];
            stack.pop(); //比当前大的都被弹出去了
            int l = stack.isEmpty()? -1 :stack.peek();
            int r = i; 
            //l和r是边界,因此区间是[l+1,r-1]
            int dist = sums[r] - sums[l+1];
            res = max(res,peak*dist);
        }
        stack.push(i); //放入当前元素索引 
	}
	
	while(!s.empty()){
        int peak = a[stack.peek()];
        stack.pop();
        int l = stack.isEmpty()? -1 :stack.peek();
        int r = n; 
        int dist = sums[r] - sums[l+1];
        res = max(res,peak*dist);
    }

for遍历
while(a[i)当前元素小于栈顶元素则出栈作为left,right就为i 然后更新结果

否则入栈索引i

769. 最多能完成排序的块 数组分隔

该数组元素特殊 0~arr.length - 1

    public int maxChunksToSorted(int[] arr) {
        if(arr==null)return 0;
        int right = arr[0];
        int cnt = 0;
        for(int i=0;i<arr.length;i++){
            right = Math.max(right,arr[i]);
            if(right==i)cnt++;
        }
        return cnt;
    }

303. 区域和检索 - 数组不可变

    private int[] sums;
    public NumArray(int[] nums) {
        sums = new int[nums.length+1];
        for(int i=0;i<nums.length;i++){
            sums[i+1] = sums[i]+nums[i]; //表示之前的元素之和
        }
    }
    
    public int sumRange(int left, int right) {
        return sums[right+1]-sums[left];
    }


304. 二维区域和检索 - 矩阵不可变

类似303. 区域和检索 - 数组不可变

class NumMatrix {
    int[][]sums;
    public NumMatrix(int[][] matrix) {
        int m = matrix.length;
        if(m>0){
            int n = matrix[0].length;
            sums = new int[m+1][n+1];
            for(int i=0;i<m;i++){
                for(int j=0;j<n;j++){
                    sums[i+1][j+1] = sums[i+1][j] + sums[i][j+1] - sums[i][j] + matrix[i][j]; 
                }
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return sums[row2+1][col2+1] - sums[row1][col2+1] -sums[row2+1][col1] + sums[row1][col1];
    }
}

240. 搜索二维有序矩阵 II


public boolean searchMatrix(int[][] matrix, int target) {
        if(matrix==null||matrix[0]==null)return false;

        int m = matrix.length;
        int n = matrix[0].length;

        int row =0, col = n-1;  //初始放在了最右上角
        while(row<m&&col>=0){
            if(matrix[row][col]==target)return true;

            if(matrix[row][col]>target){
                col--;
            }else{
                row++;
            }
        }
        return false;
    }

378. 有序矩阵中第 K 小的元素

小顶堆解法,主要结合了该排序矩阵的升序特性

public int kthSmallest(int[][] matrix, int k) {
       int m = matrix.length, n = matrix[0].length;

       PriorityQueue<Tuple> smallHeap = new PriorityQueue(); 
       for(int i=0;i<n;i++){ //先加入第0行元素
           smallHeap.offer(new Tuple(0,i,matrix[0][i]));
       }
       for(int i=0;i<k-1;i++){ //小顶堆 弹出k-1个 
           Tuple top = smallHeap.poll(); //每次弹出最小的
           if(top.x==m-1)continue;
            //将弹出元素的下一行元素加入堆中,以保证堆顶是全局次小元素
           smallHeap.offer(new Tuple(top.x+1,top.y,matrix[top.x+1][top.y]));
       }
       return smallHeap.poll().val; //得到第k小
    }

    private class Tuple implements Comparable<Tuple>{
        int x,y,val;

        public Tuple(int x,int y,int val){
            this.x = x;
            this.y = y;
            this.val = val;
        }
        @Override
        public int compareTo(Tuple that){ 
            return this.val - that.val; //从小到大
        }
    }

48. 旋转图像

matrixnew​[col][n−row−1]=matrix[row][col] 插接成水平发转和对角线翻转
在这里插入图片描述


public void rotate(int[][] matrix) {
        if(matrix==null||matrix[0]==null)return;
        int n = matrix.length;

        for(int i=0;i<n/2;i++){  //matrix[row][col]水平轴翻转matrix[n−row−1][col]
            int[] temp = matrix[i];
            matrix[i] = matrix[n-1-i];
            matrix[n-1-i] = temp;   
        }

        for(int i=0;i<n;i++){  //matrix[row][col]主对角线翻转matrix[col][row]
            for(int j=0;j<i;j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
    }

54. 螺旋矩阵

控制好两个维度,四个变量

public List<Integer> spiralOrder(int[][] matrix) {
        if(matrix==null||matrix[0]==null)return null;

        int xMax = matrix.length-1, yMax = matrix[0].length-1;

        int xMin = 0,yMin = 0;

        List<Integer>list = new ArrayList();

        while(xMin<=xMax&&yMin<=yMax){
            for(int j=yMin;j<=yMax;j++){
                list.add(matrix[xMin][j]);
            }
            xMin++;

            for(int i=xMin;i<=xMax;i++){
                list.add(matrix[i][yMax]);
            }
            yMax--;

            for(int j=yMax;j>=yMin&&xMin<=xMax;j--){  //防止上面更改了已经不再满足while循环条件
                list.add(matrix[xMax][j]);
            }
            xMax--;

            for(int i=xMax;i>=xMin&&yMin<=yMax;i--){  //防止上面更改了已经不再满足while循环条件
                list.add(matrix[i][yMin]);
            }
            yMin++;
        }

        return list;
    }

766. 托普利茨矩阵_

public boolean isToeplitzMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;

        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(matrix[i][j]!=matrix[i-1][j-1]){
                    return false;
                }
            }
        }
        return true;
    }

210. 课程表 II

核心思想:不断删除入度为0的节点

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        if(numCourses<=0)return new int[0];

        HashSet<Integer>[]adj =new HashSet[numCourses];
        for(int i=0;i<numCourses;i++){
            adj[i] = new HashSet();
        }

        int[] inDegree = new int[numCourses];
        for(int[] coursePair:prerequisites){
            adj[coursePair[1]].add(coursePair[0]); //关联coursePair[1]的后驱
            inDegree[coursePair[0]]++;  //记录coursePair[0]的入度
        }

        LinkedList<Integer>queue = new LinkedList();
        for(int i=0;i<numCourses;i++){
            if(inDegree[i]==0)queue.add(i);  //初始将所有入度为0的加入
        }

        int[] res = new int[numCourses];
        int cnt = 0;
        while(!queue.isEmpty()){
            int rmvCourse = queue.poll();
            res[cnt] = rmvCourse;
            cnt++;
            for(int course:adj[rmvCourse]){
                //inDegree[course]--; //更新入度
                if(--inDegree[course]==0)queue.add(course);  //更新入度,为0加入
            }  
        }
        //若最终数量不等,则无法完成课程排序
        if(cnt==numCourses)return res;

        return new int[0];
    }
}

易考点补充

LRU

在这里插入图片描述

通过map快速判断节点有无 增删时map和双向链表都得变动

class LRUCache {
    private HashMap<Integer,Node>map = new HashMap();
    private Node head,tail;
    private int capacity;
    private int size;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.prev = head;
    }
    
    public int get(int key) {
        Node node = map.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.val;
    }
    
    public void put(int key, int value) {
        Node node = map.get(key);
        if(node!=null){
            node.val = value;  //值替换
            moveToHead(node);
        }else{
            node = new Node(key,value);
            addToHead(node);
            map.put(key,node);
            size++;
            if(size>capacity){
                map.remove(tail.prev.key); // 去除后面的
                tail.prev.prev.next = tail;
                tail.prev = tail.prev.prev;
                size--;
            }
            
        }
    }

    private void moveToHead(Node node){
        node.prev.next = node.next;
        node.next.prev = node.prev;

        addToHead(node);
    }

    private void addToHead(Node node){
        node.next = head.next;
        node.prev = head;
        head.next.prev = node;
        head.next = node;
    }

    class Node{
        int key;
        int val;
        Node prev,next;
        public Node(){};
        public Node(int key,int val){
            this.key = key;
            this.val = val;
        }
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星空•物语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值