LeetCode

目录

简要

数组

1、考虑是否可用双指针

2、考虑是否先进行排序 nums.sort();

3、指针是否越界要格外注意

4、使用数组时经常要使用到排序

数学相关

双指针

字符串

1、常用方法

2、遍历字符串子串的方法

3、StringBuilder

动态规划

背包问题

a. 完全背包问题

b.0-1背包问题

回溯算法


简要

该文章对leetcode常用算法解法和套路进行总结。

数组

1、考虑是否可用双指针

双指针从左到右:求链表是否有环

双指针中间向两边:回文子串

双指针两边向中间:求容器乘水的最大值

2、考虑是否先进行排序 nums.sort();

如:三数之和

3、指针是否越界要格外注意

如:三数之和,回文子串

4、使用数组时经常要使用到排序

a.数组排序

Integer[] nums = new Integer[2];

Arrays.sort(nums);

Arrays.sort(nums, new Comparator<Integer>() {
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
});

b.集合排序

Collections.sort(list,new Comparator<Integer>(){

    public int compare(Integer o1, Integer o2){

        return o1-o2;
    }

});

c.集合和数组相互转换

Integer[] nums = new Integer[2];
List<Integer> list = Arrays.asList(nums);
Integer[] arr = list.toArray(new Integer[list.size()]);

d、char数组转字符串

char[] chars = {'1','2','3'};
String str = new String(chars);//123

数学相关

1、i % 10 等于i的最后一个数字。可以用来获取整数的最后一个数字。

如:123%10 = 3;12%10=2;1%10=1;0%10=0;-1%10=-1;-12%10=-2;2%10=2;

2、i/10 可以用来去掉i的最后一位。while(i != 0){} i等于0时跳出循环。

如:123/10=12;12/10=1;1/10=0;

3、求最大或最小时,经常要用到Math.min(a,b)或Math.max(a,b);

双指针

1、可以都向左遍历。如:循环链表

2、可以中间向两边。如: 回文子串

3、可以一左一右往中间。如:求容器乘水的最大值

4、同频率向一个方向,类似滑动窗口。如:删除倒数第n个链表

链表

1、可能要创建新链表

2、一般要建立头指针

3、简单且重复操作的可能可以用递归(这点在树的算法题中也适用)

86. 分隔链表 (创建新链表、并拼接)

82. 删除排序链表中的重复元素 II (链表指针判断,删除节点)

字符串

1、常用方法

如:String s = abcdefg;

int len = s.length();//求长度

String ss = s.substring(i,j);//字符串截取前闭后开 [) ;i=1 j=3 得到:bc

char[] charArray = s.toCharArray();//字符串转数组,得到{a,b,c,d,e,f,g}

char charA = s.charAt(i);//得到第i个字符,i=1 得到 b

Character.isLetterOrDigit('a');//判断字符是否是字母或数字

Character.toLowerCase(ch);//字母转小写

2、遍历字符串子串的方法

abcdef

字母从前到后,a~f,b~f,c~f,d~f,e~f,f

字母个数遍历,a,b,c,d,e,f; ab,bc,cd...;abc,bcd,cde...;abcd,bcde,cdef;.........

3、StringBuilder

sb.deleteCharAt(0); abc 变成 bc

ab.append('a'); 可以添加Character类型数据

sb.reverse();反转字符串

二分查找

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

35. 搜索插入位置

动态规划

1、找出状态转移方程式,把问题转换为子问题集。

2、一般这类问题大多都是求最大值或者最小值。

3、先求出开始的前几个值,再列出状态转移方程式,可以用一维或者二维数组把每个数据存起来。

4、二维数组可以看成一个矩阵

int[][] grid;

int row = grid.length;//

int column = grid[0].length;

int[][] dp = new int[row][column];

常见问题:62. 不同路径 、 64. 最小路径和  (解法:先填充第一行和第一列的值)

背包问题

a. 完全背包问题

零钱兑换: 凑成总数target需要的最少零钱数

  • int[] dp = new int[amount+1]; //最少零钱数
  • dp[i] = 最少零钱数; i表示要凑的零钱金额。dp[i] 初始化时为target + 1;
  • 状态转移方程式:拿走一个银币后的最小硬币数 + 1 ,1表示自己当前这个硬币,或者抽不齐;

for(int coin : coins) {

        dp[i] = Math.min(dp[i - coin] + 1,dp[i]);

}

/**
        完全背包问题
        动态规划解法:整体思路是从dp[i]里抽取出硬币,然后找到dp[i]和dp[i-coins[j]]的关系
        dp[i] = Math.min(dp[i-coins[j]] + 1, dp[i]);

        1、dp[i] 表示:总金额为i,凑成后的最少硬币个数
        
        2、如何理解状态转移方程式 dp[i] = Math.min(dp[i-coins[i]],dp[i])
        a:依次拿掉一个面值的硬币,此时面额的最少硬币数 + 1 就是dp[i]的值;
        b:由于i > i-coins[i]恒成立,所以dp[i-coins[i]] 肯定被提前计算过了
        c:如果没有符合条件的情况,就设置一个不可能的数值,如:amount+1;

        例如:输入:coins = [1, 2, 5], amount = 11
             输出:3 
             解释:11 = 5 + 5 + 1
    
     */
    public int dpMethod(int[] coins, int amount){
        if(amount == 0 || coins == null){
            return 0;
        }

        int[] dp = new int[amount+1];
        dp[0] =0;

        //1、初始化时,先设置成最大的值。(MAX = 通过面值为1的硬币进行组合)
        for(int i=1;i<=amount;i++){
            
            //amount+1后初始化的值,不可能存在,因为最大的数量就是amount,即全部为面值为1的硬币
            dp[i] = amount + 1;
        }
        
        //i表示金钱的面额
        for(int i=1;i<=amount;i++){

            //2、遍历每个硬币,并从中拿出面额
            int coinLen = coins.length;
            for(int j=0;j<coinLen;j++){
                //有符合条件的硬币数,则:当前金额-其中一个面值金额
                if(coins[j]<=i){
                    dp[i] = Math.min(dp[i-coins[j]] + 1, dp[i]);
                }
            }
            
        }
        //dp[amount] == amount+1表示该面额下,不能通过coins[j]组合出符合条件的场景,因为还是初始化时的大小
        return dp[amount] == amount+1 ? -1 : dp[amount];
    }

b.0-1背包问题

注意:因为元素不能重复使用,所以一般会二维数组表示,即i可表示可选择的元素,j为目标值。

分割等和子集:一个数组,能否分成两个和相等的数组;

1、dp[i][j]表示:数组下标在[0,i]范围内。如i=2表示 num[0]、num[1]、num[2]这个区间内的值的和使得相加后的容量等于j;j=需要凑齐的总和的值。

2、dp[i][j] 为true表示可以填充满,为fase表示不能填充满。

3、状态转移方程式:

boolean[][] dp = new boolean[nums.length][target+1];

a:上一行为true,即上一个数字就能凑成功了。所以本行肯定为true

b:上一行为false,要看把自己这个数字添加进去能不能凑成目标数(目标数为j)

dp[i][j] = dp[i-1][j] || dp[i-1][j-num[i]]

public boolean canPartition1(int[] nums) {
        if(nums == null || nums.length <= 1){
            return false;
        }

        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        //基数肯定不行
        if(sum % 2 == 1){
            return false;
        }

        int target = sum / 2;

        boolean[][] dp = new boolean[nums.length][target+1];
        //设置第一列
        for(int i=0;i<nums.length;i++){
            dp[i][0] = true;
        }
        //设置第一行
        for(int j=0;j<=target;j++){
            if(nums[0] == j){
                dp[0][j] = true;
            }else{
                dp[0][j] = false;
            }
        }
        //i为数组角标,j为要凑成的数字
        for(int i=1;i<nums.length;i++) {
           for(int j=1;j<=target;j++) {
               //上一行为true,即上一个数字就能凑成功了。所以本行肯定为true
               if(dp[i-1][j]){
                   dp[i][j] = true;
               }else{
                   //上一行为false,要看把自己这个数字添加进去能不能凑成目标数(目标数为j)
                   if(nums[i] <= j){
                       dp[i][j] = dp[i-1][j-nums[i]];
                   }
               }
           }
        }

        return dp[nums.length - 1][target];
    }

回溯算法

路走不通就回到原来的岔路口重新选择一条新的路再走。

1、先画所有组合的树形结构

2、利用递归

3、递归后把递归前的操作进行撤销(回溯)

典型例题:46. 全排列 、47. 全排列 II  、39. 组合总和  

90. 子集 II

public void dfs(int[] nums,int dept,boolean[] used,
        List<Integer> list, List<List<Integer>> result){

        //2、当遍历到最后一层后,添加数据到结果列表里
        if(dept == nums.length) {
            result.add(new ArrayList(list));
        }
        for(int i=0;i<nums.length;i++) {
            
            if(!used[i]) {
                list.add(nums[i]);
                used[i] = true;

                //1、深度遍历添加下一个数字
                dfs(nums,dept+1,used,list,result);

                //3、遍历完开始回溯,把变量替换成之前的状态
                used[i] = false;
                list.remove(list.size() - 1);

            }

        }

    }

用过的不能返回去 39. 组合总和  40. 组合总和 II

public List<List<Integer>> dfs(int[] candidates,int begin, int target,
        List<Integer> list, List<List<Integer>> result ){
     
        if(target == 0){
            result.add(new ArrayList(list));
            return result;
        }

        //begin表示开始搜索的起点。由于之前用过begin,所以后续i变化时,begin前的角标都不能使用
        for(int i=begin;i<candidates.length;i++){
            
            if(target<candidates[i]) {
                break;
            }
            list.add(candidates[i]);
            
            //由于可以元素可以重复添加,所以这里还是begin还是等于i。表示当前元素自己可以重复选择
            dfs(candidates,i,target-candidates[i],list,result);

            list.remove(list.size() -1);
        }
        return result;
 }

加大点难度:

93. 复原 IP 地址

二维数组

1、利用边界范围,或者需要注意边界范围

建立四个坐标:

int left = 0;
int right = nums[0].length -1 ;
int top = 0;
int down = nums.length -1;

59. 螺旋矩阵 II

54. 螺旋矩阵

2、建立一维或二维数组,进行辅助判断

1、79. 单词搜索

  • int[][] stepArr = {{0,1},{0,-1},{1,0},{-1,0}}; //利用这个二维数组进行移动
  • boolean[][] steped = new boolean[m][n];//通过这个二维数组,判断是否为走过的步数
  • public boolean isValidStep(int x, int y){} ;//判断边界值
  • 回溯
public boolean isValidStep(int x, int y){
     if(x>=0 && x<m && y>=0 && y<n) {
         return true;
     }
     return false;
}

2、73. 矩阵置零

通过两个一维数组,判断哪行、哪列是0?

boolean[] row = new boolean[x]; //第几行为0
boolean[] col = new boolean[y]; //第几列为0

3、36. 有效的数独

int[][] row = new int[9][10];//初始化时,记录每一行没有重复值,即:初始值为0

int[][] col = new int[9][10];

int[][] box = new int[9][10];

不可能两次为1

row[i][val] = 1;

col[j][val] = 1;

box[(i/3) * 3 + j/3][val] = 1;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值