每日一题集合

每日一题集合

    • 二叉树中的最大路径和
    • 保证文件名唯一
    • 模式匹配
    • 动态规划之最长公共子序列
    • 编辑距离
    • 二进制求和
    • 单词拆分
    • 打家劫舍
    • 打家劫舍升级版
    • 缺失的第一个正数
      • 找到所有数组中消失的数字
      • 数组中重复的数据
    • 长度最小的子数组
    • 反转链表
    • 求1+2+…+n
    • .不用加减乘除做加法
    • 构建乘积数组
    • 二叉搜索树的最近公共祖先
      • 扑克牌顺子
    • leetcode 剑指offer 67把字符串转换成整数

二叉树中的最大路径和

给定一个非空二叉树,返回其最大路径和。(6.21)

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

示例 1:

输入: [1,2,3]

   1
  / \
 2   3

输出: 6
解题思路:
二叉树 abc,a 是根结点(递归中的 root),bc 是左右子结点(代表其递归后的最优解)。
最大的路径,可能的路径情况:

   a
  / \
 b   c

b + a + c。
b + a + a 的父结点。
a + c + a 的父结点。

其中情况 1,表示如果不联络父结点的情况,或本身是根结点的情况。
这种情况是没法递归的,但是结果有可能是全局最大路径和。

情况 2 和 3,递归时计算 a+b 和 a+c,选择一个更优的方案返回,也就是上面说的递归后的最优解啦。

另外结点有可能是负值,最大和肯定就要想办法舍弃负值(max(0, x))(max(0,x))。
但是上面 3 种情况,无论哪种,a 作为联络点,都不能够舍弃。

题解:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int max=Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {

         maxPath(root);
         return max;
    }
    public int maxPath(TreeNode root){
        if(root==null){return 0;}
        //计算左边分支最大值,左边分支如果为负数还不如不选择
        int left=Math.max(0,maxPath(root.left));
        //计算右边分支最大值,右边分支如果为负数还不如不选择
        int right=Math.max(0,maxPath(root.right));
        //作为路径与历史最大值做比较
        max=Math.max(max,root.val+left+right);
        // 返回经过root的单边最大分支给上游
        return root.val+Math.max(left,right);
    }
}

`

保证文件名唯一

给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。

由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数 。

返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。

输入:names = [“kaido”,“kaido(1)”,“kaido”,“kaido(1)”]
输出:[“kaido”,“kaido(1)”,“kaido(2)”,“kaido(1)(1)”]

题解:

import java.util.HashMap;
class Solution {
    public String[] getFolderNames(String[] names) {
       HashMap<String,Integer> map=new HashMap<String,Integer>();
       String[] str=new String[names.length];
       for(int i=0;i<names.length;i++){
           int k=1;
           if(map.containsKey(names[i])){
              while(map.containsKey(names[i]+"("+k+")")){
                    k++;
                
              }
               str[i]=names[i]+"("+k+")";
               map.put(names[i]+"("+k+")",1);
           }else{
               map.put(names[i],1);
               str[i]=names[i];
           }
       }return str;
    }
}

模式匹配

你有两个字符串,即pattern和value。 pattern字符串由字母"a"和"b"组成,用于描述字符串中的模式。例如,字符串"catcatgocatgo"匹配模式"aabab"(其中"cat"是"a",“go"是"b”),该字符串也匹配像"a"、"ab"和"b"这样的模式。但需注意"a"和"b"不能同时表示相同的字符串。编写一个方法判断value字符串是否匹配pattern字符串。

示例 1:

输入: pattern = “abba”, value = “dogcatcatdog”
输出: true

示例 2:

输入: pattern = “abba”, value = “dogcatcatfish”
输出: false
每日一题集合_第1张图片
每日一题集合_第2张图片
分析解题思路——考虑边界情况——写代码

class Solution {
    public boolean patternMatching(String pattern, String value) {
       int count_a=0,count_b=0;
       for(char c : pattern.toCharArray()){
           if (c=='a'){
               count_a++;
           }else{
               count_b++;
           }
       }
    //    a的个数小于b的个数,调换a、b以保证a最少出现1次,继而枚举a
        if (count_a < count_b) {
            int temp = count_a;
            count_a = count_b;
            count_b = temp;
            char[] array = pattern.toCharArray();
            for (int i = 0; i < array.length; i++) {
                array[i] = array[i] == 'a' ? 'b' : 'a';
            }
            pattern = new String(array);
        }
        //如果主串是空字符串,并且模式串只有一种子模式或模式串也是空字符串,则匹配成功,返回 true 。
        //否则失败,返回 false
        if (value.length() == 0) {
            return count_b == 0;
        }
        //如果模式串是空字符串,并且主串非空,则匹配失败
        if (pattern.length() == 0) {
            return false;
        }
       for(int len_a=0;len_a*count_a<=value.length();++len_a){
           //主串减去 countA 个长度为 lenA 的 a 子模式匹配值后剩余字符数量。
            //剩余部分是需要和 b 模式进行匹配,由于从模式串已知 b 子模式的数量,
            //从而可以计算出b的匹配值长度(必须是非负整数)
           int res=value.length()-len_a*count_a;
           if((res==0&&count_b==0)||(count_b!=0&& res%count_b==0)){
              int len_b= (res==0? 0: res/count_b);
              String a="", b="";
              int pos=0;//每次的起始位置
              boolean correct=true;//记录当前子串是否匹配成功
              for(char c:pattern.toCharArray()){
                  if (c=='a'){
                     String sub=value.substring(pos,pos+len_a);
                     if(a.length()==0){
                         a=sub;
                     }else if(!a.equals(sub)){//如果之前取到的a的字符串和新取得的相关字段字符串不符,则跳出循环
                         correct=false;
                         break;
                     }
                     pos+=len_a;
                  }
                  else{
                      String sub=value.substring(pos,pos+len_b);//取得的当前字段值
                      if(b.length()==0){//如果是开始,把这个字段赋值给b
                          b=sub;
                      }else if(!b.equals(sub)){//在某一轮循环中,按照这个长度取得的字段不是b值
                          correct=false;
                          break;
                      }
                      pos+=len_b;
                  }
              }
              if(correct &&!a.equals(b)){///如果主串和模式串完全匹配,同时 a 子模式的匹配值和 b 子模式的匹配值不相同,则匹配成功
                  return true;
              }
           }
       }return false;
    }
}

动态规划之最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

一、动态规划思路
第一步,一定要明确 dp 数组的含义。对于两个字符串的动态规划问题,套路是通用的。

比如说对于字符串 s1 和 s2,一般来说都要构造一个这样的 DP table:

每日一题集合_第3张图片
为了方便理解此表,我们暂时认为索引是从 1 开始的,待会的代码中只要稍作调整即可。其中,dp[i][j] 的含义是:对于 s1[1…i] 和 s2[1…j],它们的 LCS 长度是 dp[i][j]。

比如上图的例子,d[2][4] 的含义就是:对于 “ac” 和 “babc”,它们的 LCS 长度是 2。我们最终想得到的答案应该是 dp[3][6]。

第二步,定义 base case。

我们专门让索引为 0 的行和列表示空串,dp[0][…] 和 dp[…][0] 都应该初始化为 0,这就是 base case。

比如说,按照刚才 dp 数组的定义,dp[0][3]=0 的含义是:对于字符串 “” 和 “bab”,其 LCS 的长度为 0。因为有一个字符串是空串,它们的最长公共子序列的长度显然应该是 0。

第三步,找状态转移方程。

这是动态规划最难的一步,不过好在这种字符串问题的套路都差不多,权且借这道题来聊聊处理这类问题的思路。

状态转移说简单些就是做选择,比如说这个问题,是求 s1 和 s2 的最长公共子序列,不妨称这个子序列为 lcs。那么对于 s1 和 s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。

每日一题集合_第4张图片
这个「在」和「不在」就是选择,关键是,应该如何选择呢?这个需要动点脑筋:如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1 和 s2 中,因为 lcs 是最长公共子序列嘛。所以本题的思路是这样:

用两个指针 i 和 j 从后往前遍历 s1 和 s2,如果 s1[i]==s2[j],那么这个字符一定在 lcs 中;否则的话,s1[i] 和 s2[j] 这两个字符至少有一个不在 lcs 中,需要丢弃一个。

https://leetcode-cn.com/problems/longest-common-subsequence/solution/dong-tai-gui-hua-zhi-zui-chang-gong-gong-zi-xu-lie/

class Solution {
    public int  longestCommonSubsequence(String text1, String text2) {
        char[] t1 = text1.toCharArray();
        char[] t2 = text2.toCharArray();
        int length1 = t1.length;
        int length2 = t2.length;
        int[][] dp = new int[length1+1][length2+1];
        for (int i = 1; i < length1 +1; i++) {
            for (int j = 1; j < length2 +1; j++) {
                if (t1[i-1] == t2[j-1]){
                    // 这边找到一个 lcs 的元素,继续往前找
                    dp[i][j] = 1+ dp[i-1][j-1];
                }else {
                    //谁能让 lcs 最长,就听谁的
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[length1][length2];
    }
}

动态规划思想,从后往前递推,写代码则从前往后写

编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

https://leetcode-cn.com/problems/edit-distance/solution/bian-ji-ju-chi-mian-shi-ti-xiang-jie-by-labuladong/

1.画出dptable表。

2.找出base状态,即当word1为“”,时,需要进行word2.length()操作,反之,要进行word1.length()的操作;
3.写出状态转移方程

写动态规划代码记得从后往前累积。
以这里的四个操作为例,写状态转移方程:
1.skip:当word1和word2中当前字符相等时,i和j同时往前跳1,同时不需要进行任何增删改查的操作:dp[i][j]=dp[i-1][j-1];
2.删除(delete)删除word1字符,则i往前走1位,j没有任何变化,故dp[i][j]=dp[i-1][j]+1(加1是因为进行了1次删除操作);
3.插入(insert)在word1里插入字符在i后一位,i不变,j往前跳1,所以dp[i][j]=dp[i][j-1]+1;
4.替换操作:替换word1里的字符为word2里的字符,此时i往前跳1位,j往前跳一位,所以dp[i][j]=dp[i-1][j-1]+1;
(因为是要找最小,所以求最小次改变次数)

class Solution {
    public int minDistance(String word1, String word2) {

       int len1=word1.length();
       int len2=word2.length();
       int [][] dp=new int[len1+1][len2+1];
       for(int i=0;i<len1+1;i++){
           dp[i][0]=i;
       }for(int j=0;j<len2+1;j++){
           dp[0][j]=j;
       }
       for(int i=1;i<len1+1;i++){
           for(int j=1;j<len2+1;j++){
               if(word1.charAt(i-1)==word2.charAt(j-1)){
                   dp[i][j]=dp[i-1][j-1];
               }else{
                   dp[i][j]=Math.min(Math.min(dp[i][j-1]+1,dp[i-1][j]+1), dp[i-1][j-1]+1);//插入和删
               }
           }
       }return dp[len1][len2];
    }
}

图片:
带尺寸的图片:
居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

二进制求和

给你两个二进制字符串,返回它们的和(用二进制表示)。

输入为 非空 字符串且只包含数字 1 和 0。

示例 1:

输入: a = “11”, b = “1”
输出: “100”
示例 2:

输入: a = “1010”, b = “1011”
输出: “10101”

 public String addBinary(String a, String b) {
       //long x= Long.parseLong(a, 2) ;   return Long.toBinaryString(x);
       //BigInteger x= new BigInteger(a,10) ;   大整数是是Java里的一个类 位运算无法操作
        int x= Integer.parseInt(a, 2) ;//二进制转十进制 
        int y=Integer.parseInt(b, 2);
        int answer =0 ,carry = 0;
        while(y>0){
            answer = x^y;
            carry = (x&y)<<1;
            x=answer;
            y=carry;
        }
        return Integer.toBinaryString(x);//十进制转二进制 

    }

Java基本类型转换:
将字符串转换为整数:int a = Integer.valueof(“1002”); //当然只能是数字类的字符串
二进制转十进制: x= Integer.parseInt(a, 2) ;
十进制转二进制:(转换后为字符串形式)Integer.toBinaryString(x);

单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
每日一题集合_第5张图片
转移方程
用指针 j 去划分两部分
[0, i]区间子串 的 dp[i+1]为真,取决于两部分:
它的前缀子串 [0, j-1]的 dp[j]为真
剩余子串 [j,i]是一个合格的单词

每日一题集合_第6张图片
base case
dp[0] = true。长度为 0 的子串是由单词表的单词组成?为什么
这纯粹是为了:让边界情况也能满足状态转移方程,即上图:当黄色部分为空字符串时,dp[i+1]全然取决于 [0,i] 子串是否是单词表的单词
所以我们让 dp[0] = true
求出 dp[i] 为真,为什么要break
因为此时不需要再用 j 去把长度 i 的这个子串分成两部分考察了
长度 i 的这个子串已经可以 break 成单词表的单词了,j 没必要继续扫描
代码如下:

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
       Set<String> wordDict_=new HashSet(wordDict);
       boolean[] dp=new boolean[s.length()+1];
       dp[0]=true;
       for(int i=1;i<=s.length();i++){
           for(int j=0;j<i;j++){
               if(dp[j] && wordDict_.contains(s.substring(j,i))){
                   dp[i]=true;
                   break;
               }
           }
       }return dp[s.length()];
    }
}

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

步骤一:定义子问题
稍微接触过一点动态规划的朋友都知道动态规划有一个“子问题”的定义。什么是子问题?子问题是和原问题相似,但规模较小的问题。例如这道小偷问题,原问题是“从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是“从 k 个房子中能偷到的最大金额”,用 f(k) 表示。

每日一题集合_第7张图片

可以看到,子问题是参数化的,我们定义的子问题中有参数 k。假设一共有 n 个房子的话,就一共有 n 个子问题。动态规划实际上就是通过求这一堆子问题的解,来求出原问题的解。这要求子问题需要具备两个性质:

原问题要能由子问题表示。例如这道小偷问题中,k=n时实际上就是原问题。否则,解了半天子问题还是解不出原问题,那子问题岂不是白解了。
一个子问题的解要能通过其他子问题的解求出。例如这道小偷问题中,f(k)可以由 f(k-1) 和 f(k-2)求出,具体原理后面会解释。这个性质就是教科书中所说的“最优子结构”。如果定义不出这样的子问题,那么这道题实际上没法用动态规划解。
小偷问题由于比较简单,定义子问题实际上是很直观的。一些比较难的动态规划题目可能需要一些定义子问题的技巧。

步骤二:写出子问题的递推关系
这一步是求解动态规划问题最关键的一步。然而,这一步也是最无法在代码中体现出来的一步。在做题的时候,最好把这一步的思路用注释的形式写下来。做动态规划题目不要求快,而要确保无误。否则,写代码五分钟,找 bug 半小时,岂不美哉?

我们来分析一下这道小偷问题的递推关系:
每日一题集合_第8张图片
步骤三:找出BASE情况
当 k=0时,没有房子,所以 f(0) = 0
当 k=1时,只有一个房子,偷这个房子即可。

步骤四:确定状态转移方程
在这里插入图片描述

class Solution {
    public int rob(int[] nums) {
          if(nums.length==0) return 0;
          if(nums.length==1) return nums[0];
          int[] dp=new int[nums.length+1];
          dp[0]=0;
          dp[1]=nums[0];
          for (int i=2;i<=nums.length;i++){
              dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i-1]);
          }return dp[nums.length];
    }
}

重点总结:
字符串类型的动态规划:梳理思路----画出dp[table]------找出base情况------确定转态转移方程
数字类型的动态规划:定义子问题----写出子问题的递推关系------找出BASE情况—确定状态转移方程

打家劫舍升级版

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

基本思路当然还是动态规划,可以划分成两种情况来做,以第一家是否被偷为依据分成两个动态规划问题,如果第一家偷,那么从第一家到第n-1家求最大值(因为意味着最后一家一定不能偷);如果第一家不偷,那么从第2家到第n家求最大值。最后再比较两种情况的最大值即可。

class Solution {
    public int rob(int[] nums) {
       //打家劫舍升级版,在这样构成了一个环,可以考虑分为偷第一间房,和不偷第一间房
       if(nums==null||nums.length==0)return 0;
       if(nums.length==1) return nums[0];
       int[] dp1=new int[nums.length];
       int[] dp2=new int[nums.length+1];
       //偷第一家的情况,不能偷最后一家,N-1
       dp1[0]=0;
       dp1[1]=nums[0];
       for(int i=2;i<nums.length;i++){
           dp1[i]=Math.max(dp1[i-1],dp1[i-2]+nums[i-1]);
       }
       //不偷第一家,可以偷最后一家,N
       dp2[0]=0;
       dp2[1]=0;
       for(int j=2;j<=nums.length;j++){
           dp2[j]=Math.max(dp2[j-1],dp2[j-2]+nums[j-1]);
       }return Math.max(dp1[nums.length-1],dp2[nums.length]);
    }
}

缺失的第一个正数

给你一个未排序的整数数组,请你找出其中没有出现的最小的正整数。

示例 1:

输入: [1,2,0]
输出: 3
示例 2:

输入: [3,4,-1,1]
输出: 2
示例 3:

输入: [7,8,9,11,12]
输出: 1

1.由于题目要求我们「只能使用常数级别的空间」,而要找的数一定在 [1, N + 1] 左闭右闭(这里 N 是数组的长度)这个区间里。因此,我们可以就把原始的数组当做哈希表来使用。事实上,哈希表其实本身也是一个数组;
2.我们要找的数就在 [1, N + 1] 里,最后 N + 1 这个元素我们不用找。因为在前面的 N 个元素都找不到的情况下,我们才返回 N + 1;
3.那么,我们可以采取这样的思路:就把 1 这个数放到下标为 0 的位置, 2这个数放到下标为 1的位置,按照这种思路整理一遍数组。然后我们再遍历一次数组,第 1 个遇到的它的值不等于下标的那个数,就是我们要找的缺失的第一个正数。
4.这个思想就相当于我们自己编写哈希函数,这个哈希函数的规则特别简单,那就是数值为 i 的数映射到下标为 i - 1 的位置。

原地哈希就相当于,让每个数字n都回到下标为n-1的家里。

而那些没有回到家里的就成了流浪汉流浪在外,他们要么是根本就没有自己的家(数字小于等于0或者大于nums.size()),要么是自己的家被别人占领了(出现了重复)。

这些流浪汉被临时安置在下标为i的空房子里,之所以有空房子是因为房子i的主人i+1失踪了(数字i+1缺失)。

因此通过原地构建哈希让各个数字回家,我们就可以找到原始数组中重复的数字还有消失的数字。

class Solution {
    public int firstMissingPositive(int[] nums) {
           int len=nums.length;
           for(int i=0;i<len;i++){
               while(nums[i]>0 && nums[i]<=len &&nums[nums[i]-1]!=nums[i]){
               //交换完后的数字还会进入这次循环,再判断是否可以交换
                   swap(nums,nums[i]-1,i);
               }
           }for(int i=0;i<len;i++){
               if(nums[i]-1!=i){
                   return i+1;
               }
           }return len+1;
       
    }
    public void swap(int[] nums,int index1,int index2){
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
}

找到所有数组中消失的数字

(与缺失的正整数一个做法思路,对了,在做题中,遇到接口为list,可以调用arraylist与linkedList)
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[5,6]

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
          int len=nums.length;
          List<Integer> list=new ArrayList<Integer>();
          for(int i=0;i<len;i++){
              while(nums[nums[i]-1]!=nums[i]){
                  swap(nums,i,nums[i]-1);
              }
          }for(int i=0;i<len;i++){
              if(nums[i]-1!=i){
                  list.add(i+1);
              }
          }return list;
    }public void swap(int[] nums,int index1,int index2){
        if (index1==index2) return;
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
}

数组中重复的数据

与缺失的第一个正数属于同一类型题
给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。

找到所有出现两次的元素。

你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?

示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[2,3]

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        int len=nums.length;
        List<Integer> list=new ArrayList<Integer>();
        if(len==0){return list;}
        for(int i=0;i<len;i++){
              while(nums[nums[i]-1]!=nums[i]){
                  swap(nums,nums[i]-1,i);
              }
        }for(int i=0;i<len;i++){
              if(nums[i]-1!=i){
                  list.add(nums[i]);
              }
        }return list;
    }public void swap(int[] nums,int index1,int index2){
        if (index1==index2) return;
        int temp=nums[index1];
        nums[index1]=nums[index2];
        nums[index2]=temp;
    }
}

长度最小的子数组

一般这种求连续子数组的都要使用滑动窗口思想。

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
每日一题集合_第9张图片

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

反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

class Solution {
    public ListNode reverseList(ListNode head) {
          if(head==null||head.next==null){
              return head;

          } 
          ListNode real_head=head;
          ListNode tail=real_head.next;
          ListNode value= reverseList(tail);
          tail.next=real_head;
          real_head.next=null;
          return value;
    }
}

求1+2+…+n

求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

/&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,

class Solution {
    public int sumNums(int n) {
       int sum=n;
       Boolean b=(n>0) && (sum+=sumNums(n-1))>0;
      // &&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,
       return sum;
    }
}

.不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
1.两个二进制相加的结果是用异或实现的,执行加法操作X ^Y;
2.两个二进制的进位结果是由一个与门来实现的,进位操作(x&y)<<1
(即先加再进位,再对加完的结果和进位的结果再加再进位,直到进位的结果为0,说明不需要再进位,此时得到的加的结果是和)

class Solution {
    public int add(int a, int b) {
          int result,carry;
          do{
              result=(a^b);
              carry=(a&b)<<1;
              a=result;
              b=carry;
          }while(carry!=0);
          return result;
    }
}

构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

class Solution {
    public int[] constructArr(int[] a) {
        int len=a.length;
        
        int[] b=new int[len];
        if(len<=1){return b;}//考虑输入为[]的情况,应当返回[]
        b[0]=1;
        for(int i=1;i<=len-1;i++){
            b[i]=b[i-1]*a[i-1];//因为数组长度为n,所以一定要计算到n-1
        }
        int temp=1;
        for(int j=len-1;j>=0;j--){//必须为大于等于0,,否则会无法计算B[0]的值
            b[j]=b[j]*temp;
            temp=temp*a[j];
        }
        return b;
    }
}

思路如下图:
每日一题集合_第10张图片

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

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
每日一题集合_第11张图片

题解:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/solution/mian-shi-ti-68-i-er-cha-sou-suo-shu-de-zui-jin-g-7/

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while(root != null) {
            if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
                root = root.right; // 遍历至右子节点
            else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
                root = root.left; // 遍历至左子节点
            else break;
        }
        return root;
    }
}


扑克牌顺子

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。

class Solution {
    public boolean isStraight(int[] nums) {
        TreeSet<Integer> set=new TreeSet<>();
        int count=0;
        for(int num:nums){
            if(num==0)count++;
            else{
                set.add(num);
            }
        }
        if(set.size()+count==5 && set.last()-set.first()<5){
            return true;
        }
        return false;

    }
}

每日一题集合_第12张图片

leetcode 剑指offer 67把字符串转换成整数

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

class Solution {
    public int strToInt(String str) {
    	//先去空格再判空,不然" "教您做人,血的教训
        str = str.trim();
        if(str.length() == 0){
            return 0;
        }
        //然后我想啊,下面要判断首位了
        //首位合格的无非就'+'或'-'或数字三种情况,其他的一概滚蛋
        //'+''-'肯定是要把它去掉的,这样三种情况就统一了
        //当然了,'-abc'这种有可能出现,不过只看首位它是没毛病的
        //让它进来,反正后面很容易解决
        //既然要去掉正负号,那肯定要出个boolean记一下是不是负数
        boolean isMinus = false;
        char[] ch = str.toCharArray();
        //首位是不是正负号或者数字啊
        if(ch[0] == '+' || ch[0] == '-' || Character.isDigit(ch[0])){
        	//是不是正负号啊
            if(ch[0] == '+' || ch[0] == '-'){//这一步一定要有正号和负号或判断,否
            //则下一个while循环+1,就会无法通过Character.isDigit(ch[index])判断,因为正号并不是数字;
            //是不是负号啊
                if(ch[0] == '-'){
                    isMinus = true;
                }
                //删除首位
                ch = Arrays.copyOfRange(ch,1,ch.length);
            }
            //首位搞定了就看后面是不是数字了,直到不是数字的地方或者倒底结束
            int index = 0;
            //结果可能超int范围,拿个long接一下
            //'-abc'这种情况返回的也是0,舒服,一箭双雕
            long res = 0;
            //短路与助您远离空指针喔,铁汁们,先后顺序关注一下
            while(index < ch.length && Character.isDigit(ch[index])){
                //一位一位往上算
                res *= 10;
                res += ch[index] - '0';
                //及时止损,一看到res超int范围立马return
                //你要是想着最后一起算,那肯定会有超long范围的测试用例等着你,你就哭去吧
                if(res > Integer.MAX_VALUE){
                    //正负号看是正数负数,返回最大值
                    return isMinus ? Integer.MIN_VALUE : Integer.MAX_VALUE;
                }
                //别忘了往后走一位
                index++;
            }
            //long转int就是这么朴实无华
            return isMinus ? -(int)res : (int)res;
        }
        //兄弟首位都不对想啥呢,回去吧您
        return 0;
    }
}

你可能感兴趣的:(每日一题集合)