LeetCode的奇妙刷题之旅

LeetCode题目学习笔记

题目来源https://leetcode-cn.com/problems/
仅作学习记录用

目录

        • 1.两数之和
        • 18.四数之和
        • 996.正方形数组的数目
        • 17.电话号码的字母组合
        • 5.最长回文子串
        • 64.最小路径和
        • 62.不同路径
        • 91.解码方法
        • 207. 课程表
        • 225. 用队列实现栈

1.两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:

第一反应是嵌套遍历,第一层遍历钉死一个索引,

通过减法获取另一个符合需求的值,在进行嵌套遍历找到符合该值的索引

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] indexs=new int[2];
        
        for(int i=0 ; i<nums.length;i++)
        {
            int a= nums[i];
            int b= target-a;
            for(int j=i+1; j<nums.length;j++)
            {
                if(nums[j]==b ){
                    indexs[0]=i;
                    indexs[1]=j;
                }
            }
        }
        return  indexs;
    }
}

18.四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]


思路:

将四数之和转为三数之和的情形,

通过一次嵌套遍历,确定了前两个数值(for{for{}} -> a,b),

然后通过双指针,进行一左一右的遍历(因为排完序之后,一左一右同时遍历可以减少一次枚举)(left,right)

为了避免重复,在确定前两个数值的时候,要注意避开相同数值,因为相同的数值会导致结果相同

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        ArrayList<List<Integer>> res = new ArrayList<>();
            Arrays.sort(nums);
            for (int i = 0 ; i<nums.length-3;i++){
                if (i>0&&nums[i] == nums[i-1])
                    continue;
                //取值相同的需要跳过
                for (int j=i+1;j<nums.length-2;j++){
                    //取值相同的需要跳过
                    if (j>i+1&&nums[j] == nums[j-1])
                        continue;
                    int left=j+1;//左指针
                    int right=nums.length-1;//右指针
                    while (left<right){
                        int sum=nums[i]+nums[j]+nums[left]+nums[right];//取和
                        if (sum==target){
                            List<Integer> ints1 = Arrays.asList(nums[i],nums[j],nums[left],nums[right]);
                            res.add(ints1);
                            while (left<right&& nums[left]==nums[left+1]){
                                //遍历相同数值没有意义,所以跳过相同数值
                                left++;
                            }
                            left ++; 
                            //因为数值是排序的,所以在前两个数值钉死的前提下,
                            //双指针一增一减才能保持平衡,才有可能取和等于target
                            while (left<right && nums[right] == nums[right-1]){
                                right--;
                            }
                            right--;//因为数值是排序的,所以在前两个数值钉死的前提下
                            //双指针一增一减才能保持平衡,才有可能取和等于target

                        }
                        else if(sum > target){//当取和大于目标值时,减少右指针
                            right--;
                        }
                        else {
                            left++;
                        }
                    }
                }
            }
            return res;
        }
    }

996.正方形数组的数目

给定一个非负整数数组 A,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。返回 A 的正方形排列的数目。两个排列 A1 和 A2 不同的充要条件是存在某个索引 i,使得 A1[i] != A2[i]。

输入:[1,17,8]
输出:2
解释:
[1,8,17] 和 [17,8,1] 都是有效的排列。
​
输入:[2,2,2]
输出:1

思路

1.如何获取两个相邻的(相加)等于完全平方数
2.如何将符合条件的值拼凑成符合条件的数组
以图的思路来解题的话:
把数组中的每个元素当作是图的顶点,然后把和顶点之间符合条件的给连成线,这样便可以获得一个图了,然后判断每条线的长度与目标数组的长度相等时,便是符合条件的数组

class Solution {
    public int numSquarefulPerms(int[] arr) {
        int len = arr.length; //目标数组长度
​
        HashMap<Integer, Integer> counts = new HashMap<>();//记录每个元素的剩余调用次数
        HashMap<Integer, ArrayList<Integer>> graphs = new HashMap<>();//记录每个源数的符合条件的集合//初始化counts
        for (int key : arr) {
            if (counts.get(key) == null) {
                counts.put(key, 1);
            } else {
                counts.put(key, counts.get(key) + 1);//获得每个元素的个数
            }
        }
        //初始化graph
        for (int key : counts.keySet()) {
            graphs.put(key, new ArrayList<Integer>());
            for (int key1 : counts.keySet()) {
                if (key1 == key && counts.get(key1) == 1) {
                    continue;
                } else {
                    int r = (int) (Math.sqrt(key + key1) + 0.1);
                    if (r * r == (key + key1)) {
                        graphs.get(key).add(key1);//获得所有元素中各自符合条件的元素集合
                    }
                }
            }
        }//使用递归的方式进行构建数组
        //这里采用图的思想,
        // 以每个元素作为顶点,
        // 与每个符合条件的元素连线,
        // 最终连线的长度等于目标数组的长度则为符合条件的数组
        int res = 0;//符合条件的数组的个数for (int key : counts.keySet()) {
            res += complete(key, len - 1, counts, graphs);
        }
        return res;
        }
    
    private int complete(int key, int i, HashMap<Integer, Integer> counts, HashMap<Integer, ArrayList<Integer>> graphs) {
​
        counts.put(key, counts.get(key) - 1);
        //如果符合调教结果数量+1,所以递归出口是当连线剩余长度为0的时候
        int res = 1;
        if (i != 0) {//如果不等于0,则需要继续递归寻找符合条件的下个元素
            res = 0;//因为不满足,所以重置为0
            for (int key1 : graphs.get(key) ){
                //遍历出当前key中,符合条件的元素集合
                if(counts.get(key1)==0){
                    continue;
                }
                res+=complete(key1, i - 1, counts, graphs);
            }}
        counts.put(key, counts.get(key) + 1);
        return res;
    }
}

17.电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。
LeetCode的奇妙刷题之旅_第1张图片

注意
1 不对应任何字母。

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

思路:
根据传入的数组,列出其组合
用(伪)树结构帮助理解,假如传入的是23,那么2作为根节点,3作为子节点
然后由上往下张开,a->3,b->3,c->3
a->3=a->d,a->e,a->f 穷举出所有组合(递归)
和上一题的996有点相似


      class Solution {
   public List<String> letterCombinations(String digits) {
        
       // 初始化2-9的元素
       HashMap<Integer, String> eleMaps = new HashMap<>();//用来存放每个数字对应的字母
       String ele = "abcdefghijklmnopqrstuvwxyz";
       int index = 0;
       //初始化eleMaps
       for (int i = 2; i < 10; i++) {
           int start = index;
           int end = index += 3;
           if (i == 7 || i == 9) {
               end = index += 1;
           }
           String substring = ele.substring(start, end);
           eleMaps.put(i, substring);
       }
       StringBuilder stringBuilder = new StringBuilder();//用来存每一种可能性
       ArrayList<String> sbList = new ArrayList<>();//收集每种可能性
       ArrayList<String> arrayList = compete17(eleMaps, 0, stringBuilder, sbList, digits);
       return arrayList;
   }
   private ArrayList<String> compete17(HashMap<Integer, String> eleMaps, int i, StringBuilder stringBuilder, ArrayList<String> sbList, String dest) {
       if (i == dest.length()) {//递归出口
           if (dest.length()!=0)
           //判断当前解析的长度是与输入的个数一致 如果输入为空的话则是空字符串
           sbList.add(stringBuilder.toString());
       } else {
           //获得当前的数字
           String curr = String.valueOf(dest.charAt(i));
           for (String key : eleMaps.get(Integer.valueOf(curr)).split("")) {//遍历当前数字下的字母
               stringBuilder.append(key);//添加当前数字下的第一个字母,
               compete17(eleMaps,i+1,stringBuilder,sbList,dest);
               //为了下一次遍历,需要把上一次添加的剔除出去
               stringBuilder.deleteCharAt(stringBuilder.length()-1);
           }
       }
       return sbList;
   }
}

5.最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
​
示例 2:
​
输入: "cbbd"
输出: "bb"

思路:
拿到题,第一思路就是能不能暴力穷举出来,
结果是可以的,
我考虑到要求的是最长,那么我从回文长度最长=字符串长度进行遍历,将遍历出来的字符串进行判定是否符合要求
结果就是超时
(因为字符串长度特别大,然后里面如果符合要求的只是单字符串的话,那么需要进行的遍历迭代次数就爆炸增长了)
错误例子:

public class LeetCode{    
    public  void test05(){
​
        String dest="abaada";
        String[] split = dest.split("");
        int len = dest.length();for (int index=len;index>=0;index--){
            for (int i=0 ;i <= len-index;i++){
                int startindex=i;
                int endindex=i+index;
                String substring = dest.substring(startindex, endindex);if(complete05(substring)){
                    System.out.println(substring);
                    return;
                }
//                System.out.println(substring);
            }}
        private boolean complete05(String substring) {
        int len = substring.length();
        String[] split = substring.split("");
        for (int i= 0,j=len-1 ;i<len && j>=0;i++,j--){
            if (i==j || j<i ){
//                break;
                return true;
            }else {
                if (split[i] .equals(split[j]) ) {
//                    System.out.println(split[i]+":"+split[j]);
                    continue;
                } else {
                    return false;
                    /*System.out.println("非回文字符串");
                    break;*/
                }
            }}
        return false;
    }
}

思路:
正确的思路可以使用动态规划,
通过一个二维数组,从最小长度的回文字符串,
记录对应的起始index和结尾index,对应的符合为true,
不符合为false这样在不断添加回文字符串长度进行遍历的时候,
只需要通过二维数组查询结果,就可以减少多余的重复遍历

class Solution {
    public String longestPalindrome(String dest) {
        //String dest="aba";
        int len = dest.length();
        String ans="";//用来记录结果字符串
        Boolean[][] arr=new Boolean[len][len];//用来记录每种规划的符合情况for (int i = 0 ;i<len;i++){//第一层遍历,用来定义回文字符串的长度(-1):0->1->2->3
            for (int j=0;j+i<len;j++){//第二层遍历,用来定义在该长度下的所有情况if (i==0){
                    //当i=0时,单字符串一定是回文字符串
                    arr[j][j+i]=true;
                }else if (i==1){
                    //当i=1时,需要判断每两个字符之间是否相等来判断是否回文字符串
                    arr[j][j+i]=(dest.charAt(j)==dest.charAt(j+i));
                }else {
                    //当i>1时,说明回文长度>2了,需要通过动态规划的方式
                    //这边通过arr数组,记录了回文长度0->2(n)有哪些符合的了
                    arr[j][j+i]=(dest.charAt(j)==dest.charAt(j+i) && arr[j+1][j+i-1]);
                }
                if(arr[j][j+i] && i+1>ans.length()){
                    ans=dest.substring(j,j+i+1 );}}
            
        }
        return ans;
    }
}

64.最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。示例:输入:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

思路:
用一个新的是数组在记录走不同路线会得到的最小值
由于限制条件只能向下或向右,这样,就可以限定了横竖靠边那两排的最小只能来自于同一排或者同一列的上一个
非靠边的路线,需要通过比较通过向右向下得来的数的大小,来进行累加,
这样就可以得到最后的数了
动态规划:每一步,都需要通过对应的条件规划累加的方法

class Solution {
    public int minPathSum(int[][] arr) {//m*n的数组--> 只能向左或者向右求最小-->所以只需要走(m+n)-2步
        //所以每个阶段都要求最小
        //假设我现在已经在arr[m][n]了,那我的上一个必须是最小,我这里才能最小
        //而我的上一个是arr[m-1][n]或者arr[m][n-1]//int[][] arr = new int[][]{{1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
        int[][] arr2 = new int[arr.length][arr[0].length];int ans = 0;for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[0].length; j++) {
                //初始化第一个
                if (i==j && i==0){
                    arr2[0][0]=arr[0][0];
                }
                else if (i==0){
                    //靠边那两排的数,只能从同一边的上一个求和的来
                    arr2[i][j]=arr2[i][j-1]+arr[i][j];
                }else if (j==0){
                    arr2[i][j]=arr2[i-1][j]+arr[i][j];
                }else {
                    //非靠边的数,需要判断通过向右或向下而来的大小进行赋值
                    arr2[i][j]=Math.min(arr2[i-1][j],arr2[i][j-1] )+arr[i][j];
                }}
        }
        ans=arr2[arr.length-1][arr[0].length-1];
        return ans;
    }
}

62.不同路径

一个机器人位于一个 m x n 网格的左上角
(起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。
机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
LeetCode的奇妙刷题之旅_第2张图片

示例 1:
​
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
​
示例 2:
​
输入: m = 7, n = 3
输出: 28

​思路:
由于先做了上面的最小路径和,所以很快可以采用类似的算法进行解答
通过一个新的数组,用来记录到达该节点的可行数,
从起始点依次累加通过规划规则可以看出,
靠边的节点,来源只能来源一个方向,
所以靠边的不进行累加非靠边的可以从上来也可以从左来,一次计算累加,
可以得出答案

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] arr2 = new int[m][n];
        int ans = 0;
        //***
        //***
        //
        //通过统计到达每个点的可能性有多少种来计算路径数
        //贴边的路径只能从左边或者上面来
        for (int i=0;i<arr2.length;i++){
            for (int j=0;j<arr2[0].length;j++){//初始化起点
                if (i==0&& i==j){
                    arr2[0][0]=1;
                }
                else if (i==0){
                    //靠边路径只有一种来源
                    arr2[i][j]=arr2[i][j-1];
                }else if (j==0){
                    //靠边路径只有一种来源
                    arr2[i][j]=arr2[i-1][j];}else {
                    //非靠边路径可以从左,或者从上来
                    arr2[i][j]=arr2[i-1][j]+arr2[i][j-1];
                }}
            
        }
            ans=arr2[m-1][n-1];
            return ans;
}
}

91.解码方法

一条包含字母 A-Z 的消息通过以下方式进行了编码:‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26给定一个只包含数字的非空字符串,请计算解码方法的总数。题目数据保证答案肯定是一个 32 位的整数。

示例 1:
​
输入:"12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。
​
​
示例 2:
​
输入:"226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

​思路:
还是采用动态规划,
需要考虑的情况有点多,
1位数的有效值是1-9,
2位数的有效值是10-26
通过对有效值的过滤判断,将遍历到每一位的可能性记录下来需要特殊考虑的是
当传值长度=0,1,2的时候需要特殊考虑其它情况通过
1.当前值有效则取上一位数的可能性个数
2.(当前值+上一个值)有效时,则把这两个当作一个整体,取上上个数的可能性个数
两者相加就是当前值的可能性个数​

class Solution {
    public int numDecodings(String dest) {
         //String dest = "123";int[] arr = new int[dest.length()];//用来存储每个位置的可能性char[] chars = dest.toCharArray();
        if (chars[0] == '0' || chars.length == 0) {
            return 0;
        }
        arr[0] = 1;//如果第一位不是为0,说明有效数值可能性+1
        for (int i = 1; i < chars.length; i++) {
            //遍历的时候,优先判断当前值是否有效(0为无效)
            if (chars[i] != '0') {
                arr[i] = arr[i - 1];
            }
            int num = ((chars[i] - '0')) + 10*(chars[i - 1] - '0');
            if (num >= 10 && num <= 26) {
                //此时说明当前值与上一个值可以组成有效值
                //这种情况由于相当于上个值和当前值当作一体了,所以需要加上的是上上个的可能性
                //由于第二个数没有上上个值,所以特殊处理
                if (i == 1) {
                    //当遍历第二个的时候,如果可以和第一个组成有效值时
                    arr[i] += 1;
                } else {
                    arr[i] += arr[i - 2];
                }
            }
        }
        return arr[chars.length-1];
    }
}

207. 课程表

你这个学期必须选修 numCourse 门课程,记为 0 到 numCourse-1 。
在选修某些课程之前需要一些先修课程。
例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:
​
输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。
​
示例 2:
​
输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

思路:
通过判断该关系图是否符合DAG(有向无环图)先将要修的课程和其依赖的课程进行连线(List(List))
然后遍历所有课程,通过给每个次遍历的课程打上标签0(未搜索),1(以搜索),2(正在搜索)
只有是处于0也就是为搜索的状态,才会进一步进行递归搜索
如果搜索到处于正在搜索状态的课程时,此时处于闭环状态—不符合题意​

class Solution {
    List<List<Integer>> edges;
    int[] visited;
    boolean valid = true;public boolean canFinish(int numCourses, int[][] prerequisites) {
        edges = new ArrayList<List<Integer>>();
        //初始化每条边
        for (int i = 0; i < numCourses; i++) {
            edges.add(new ArrayList<Integer>());
        }
        //给每条边连线
        for (int[] info : prerequisites) {
            int learn = info[0];//要选修的课程
            int dep = info[1];//需要先修完该课程优先
            edges.get(learn).add(dep);//获得每个课程,及其依赖的课程
        }
        /*
         *       dep
         *  learn1  learn2
         *  如果learn1 和 learn2 遍历完都没有问题,则dep
         * */
        visited = new int[numCourses];//初始化遍历 初始化为0:为未搜索   1:为搜索完了    2:为搜索中(如果遍历到处于搜索中的则为回环)
        for (int i = 0; i < numCourses; i++) {
            //传入当前遍历的起点
            complete(i, visited, edges);
        }
        System.out.println(valid);
        return valid;
    }private void complete(int i, int[] visited, List<List<Integer>> edges) {
        visited[i] = 2;//初始化为搜索中
        for (int j : edges.get(i)) {//遍历依赖的课程
            if (visited[j] == 2) {
                //遍历到处于搜索中的课程,形成回环
                valid =false;
                return ;
            } else if (visited[j] == 0) {//遍历到未搜索过的课程
                 complete(j,visited,edges);}
        }
        visited[i]=1;
        return ;
    }
}

225. 用队列实现栈

使用队列实现栈的下列操作:

push(x) -- 元素 x 入栈
pop() -- 移除栈顶元素
top() -- 获取栈顶元素
empty() -- 返回栈是否为空
注意:

你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

思路:

由于栈的出栈顺序和队列是相反的,所以可以通过两个队列的形式,没添加一个元素,就将一个队列的元素追加到新队列里面,从而实现栈结构的先进后出


    class MyStack {
     Queue<Integer> queue1;
        Queue<Integer> queue2;
    
        /** Initialize your data structure here. */
        public MyStack() {
            queue1 = new LinkedList<Integer>();//用来操作的队列
            queue2 = new LinkedList<Integer>();//用来中转的队列
        }
        
        /** Push element x onto stack. */
        public void push(int x) {
            queue2.offer(x);//中转队列添加元素
            while (!queue1.isEmpty()) {
                queue2.offer(queue1.poll());//将操作队列中的元素依次追加到中转队列中
            }
            Queue<Integer> temp = queue1;//两个队列进行反转,这样队列1就符合栈结构的顺序
            queue1 = queue2;
            queue2 = temp;
        }
        
        /** Removes the element on top of the stack and returns that element. */
        public int pop() {
            return queue1.poll();
        }
        
        /** Get the top element. */
        public int top() {
            return queue1.peek();
        }
        
        /** Returns whether the stack is empty. */
    
    
        /** Returns whether the stack is empty. */
        public boolean empty() {
            return queue1.isEmpty();
        }
    }

你可能感兴趣的:(LeetCode,leetcode,算法)