LeetCode-hot100题解—Day1

原题链接:力扣热题-HOT100
题解的顺序和题目的顺序一致,那么接下来就开始刷题之旅吧

1.两数之和

思路1:暴力枚举法
暴力枚举法很简单,遍历nums数组的每一个元素,找到和target-nums[i]相同的元素即可。
时间复杂度:需要使用到两层for循环,所以时间复杂度为O(n^2)
代码实现

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

思路2:哈希表法
使用哈希表来解决该问题的思路是,创建一个哈希表来存放数据,其中keytarget-nums[i]value为对应的索引。在遍历数组时,将数组中元素的值和对应的索引记录到哈希表中,检查当前元素的补数(target-nums[i])是否在哈希表中,如果在,其value即为所求,若不在,将元素记录到哈希表中并接着遍历数组。
时间复杂度:只需要遍历一次数组,用到一个for循环,所以时间复杂度为O(n)
代码实现

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> tab = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            int tar = target-nums[i];
            if(tab.containsKey(tar)){
                return new int[] {i,tab.get(tar)};
            }else{
                tab.put(nums[i],i);
            }
        }
         return null;
    }
}

2.两数相加

思路:
使用初等数学的直接相加即可,主要考察对链表的操作,在相加的过程中如果链表的长度不一样,在较短的链表后面补0即可,使用carry来记录进位,carry不为0时表示需要进行进位操作,carry的值使用sum/10,创建新结点的值使用sum%10
注:详细的解题思路参见图解-两数相加
时间复杂度
该思路的时间复杂度为O(n+m),空间复杂度也为O(n+m),其中n和m分别为两链表的长度。
代码实现

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(-1);
        ListNode tail = head;
        int sum;
        int carry = 0;
        while(l1 != null || l2 != null){
            int n = l1 == null ? 0 : l1.val;
            int m = l2 == null ? 0 : l2.val;
            sum = n + m + carry;

            carry = sum / 10;

            tail.next = new ListNode(sum % 10);
            tail = tail.next;
            l1 = l1 != null ? l1.next : null;
            l2 = l2 != null ? l2.next : null;
        }
        if(carry != 0){
            tail.next = new ListNode(carry);
        }
        return head.next;
    }
};

3.无重复字符的最长子串

思路:
该题考察的是滑动窗口的相关知识,首先我们要考虑字符串为空的情况,这是最容易忽略的一个点,用一个简单的判断语句即可解决。排除了这种情况之后我们创建一个哈希表,初始化左指针leftmax的值为0,使用for循环来遍历整个字符串,首先,判断当前字符是否在map中,如果不在,则将该字符添加到map(字符,字符索引),此时没有出现重复的字符,所以left不需要变化,无重复子串的最大长度为i-left+1,与原来max比较取最大值。如果map中包含字符,则会出现两种情况:

  • 当前字符包含在有效子段中,那么此时需要更新left的值(left=map.get(s.charAt(i))+1
  • 当前字符不在有效子段中,我们发现此时,更新left的结果同第一种情况

综上,我们使用left=Math.max(left,map.get(s.charAt(i))+1)更新left的值。对于max的值每次都与i-left+1作比较,取最大值。
注:如果对上面的思路不是很理解,可以参考详解思路-无重复字符的最长子串
时间复杂度
使用了一层for循环,所以时间复杂度为O(n)
代码实现

class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character,Integer> map =new HashMap<Character,Integer>();
        if(s.length()==0){
            return 0;
        }
        int max=0;
        int left=0;
        for(int i = 0;i<s.length();i++){
            if(map.containsKey(s.charAt(i))){
                left = Math.max(left,map.get(s.charAt(i))+1);
            }
            map.put(s.charAt(i),i);
            max=Math.max(max,i-left+1);
        }
        return max;
    }
}

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

思路
该题的解决采用二分思想,只需要给出两个有序数组一个恰当的【分割线】,中位数的值就由位于这个【分割线】的两侧的数来决定,确定分割线的位置使用二分查找法,需要注意的点是:分割线左边的所有元素的数值<分割线右边所有元素的数值
注:这里的思路写的比较简单,详细的可以看这个视频详细思路-寻找两个正序数组的中位数
时间复杂度
时间复杂度为O(log min(m,n)),m,n分别为两数组的长度,空间复杂度为O(1)
代码实现:

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        //将nums1设置为长度较小的数组,方便代码的编写
        if(nums1.length>nums2.length){
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }
        
        int m = nums1.length;
        int n = nums2.length;
        //计算分割线左边元素的数量
        int totalleft = (m+n+1)/2;
        
        //二分法查找nums1部分的分割线
        int left = 0;
        int right = m;
        while(left<right){
            //这是对于(left+right)/2的特殊处理方式,防止发生整型溢出
            //同时使用二分法时,如果出现left=i,则这里需要+1,否则不需要
            int i = left+(right-left+1)/2;
            int j = totalleft - i;
            if(nums1[i-1] > nums2[j]){
                right = i - 1;
            }else{
                left = i;
            }
        }
        int i = left;
        int j = totalleft - i;
        //最后得到两个数组分割线左右两边元素的最大值的最小值
        //为了防止出现分割线左右两边没有元素的极端情况加上判断
        int nums1LeftMax = i ==0 ? Integer.MIN_VALUE : nums1[i-1];
        int nums1RightMin = i ==m ? Integer.MAX_VALUE : nums1[i];
        int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j-1];
        int nums2RightMin = j == n ? Integer.MAX_VALUE : nums2[j];
        //计算中位数的值
        if(((m+n) % 2) == 1){
            return Math.max(nums1LeftMax,nums2LeftMax);
        }else{
            return (double) ((Math.max(nums1LeftMax,nums2LeftMax) + Math.min(nums1RightMin,nums2RightMin))) / 2;
        }
    }
}

5.最长回文子串

本题官方共给出了四种解决方法,分别为暴力解法,中心位置法,动态规划法和Manacher方法,在这里主要介绍动态规划法这种最常用的方法,对于暴力解法,只给出判断回文子串的核心代码。对其他两种解法感兴趣的可以点击四种解法-最长回文串观看详细视频。
思路1
暴力解法遍历字符串即可,其中判断回文子串的核心代码如下:

private boolean validPalindromic(char[] charArry,int left,int right){
	while(left<right){
		if(charArry[left] != charArry[right]){
			return false;
		}
		left++;
		right--;
	}
	return true;
}

思路2
动态规划法:动态规划法的关键在与确定状态转移方程,在本题中,如果一个字符串的最左边和最右边的字符相同,那么说明该字符串是否为回文串取决于其中间部分的子串,我们可以继续比较中间子串的最左边和最右边字符是否相同,依此类推…。我们使用一个二维数组dp[i][j]来表示子串s[i...j]是否为回文子串,那么状态转移方程为dp[i][j]=(s[i])==s[j]) && dp[i+1][j-1]。边界条件为j-i<3(当j-i为1或者2的时候字符串中间分别为0或者1个元素,不需要进行判断,如ab,aba),初始化数组dp,由于对角线位置s[i]==s[j],因此对角线位置全为true,我们记录下最长回文串的长度和初始位置,即可进行回文串的截取。
时间复杂度
该解法的时间复杂度为O(n^2)(n为字符串长度),空间复杂度为O(n^2)
代码实现

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        if(len<2){
            return s;
        }

        int maxLen = 1;
        int begin = 0;
        //初始化二维数组
        boolean[][] dp = new boolean[len][len];
        for(int i=0;i<len;i++){
            dp[i][i] = true;
        }

        char[] charArray = s.toCharArray();
        for(int j=1;j<len;j++){
            for(int i=0;i<j;i++){
                if(charArray[i] != charArray[j]){
                    dp[i][j]=false;
                }else{
                    if(j-i<3){
                        dp[i][j]=true;
                    }else{
                        dp[i][j] = dp[i+1][j-1];
                    }
                }
                if(dp[i][j] && j-i+1 > maxLen){
                    maxLen = j-i+1;
                    begin = i;
                }
            }
        }
        return s.substring(begin,begin+maxLen);
    }
}

6.Z字形变换

思路
该题的解题思路是首先创建一个空数组,长度为numRows,然后遍历字符串,向数组中放入字符,首先放入的方向是朝下的,如图,A放在0,B放在1,C放在2,然后需要转换方向,设置一个参数down用于表示方向的转换,转换方向的条件是前面设置的数组的下表row==0或者row==numRows-1,转换后更新一下row的值,向下时row每次+1,向上时row每次-1,这样就把每个字符拼接到数组中,最后将遍历到string类型的结果值中并返回即可。如果上述思路没有很明白,可以点击视频详解-Z字形变换学习。
在这里插入图片描述
时间复杂度
使用到一层for循环,时间复杂度为O(n^2)
代码实现

class Solution {
    public String convert(String s, int numRows) {
        if (numRows == 1) {
            return s;
        }
        ArrayList<String> rows = new ArrayList<>(numRows);
        for (int i = 0; i < numRows; i++) {
            rows.add("");
        }
        boolean down = false;
        for (int i = 0, row = 0; i < s.length(); i++) {
            rows.set(row, rows.get(row) + s.charAt(i));
            if (row == 0 || row == numRows - 1)
                down = !down;
            row += down ? 1 : -1;
        }
        String ans = "";
        for (int i = 0; i < numRows; i++)
            ans += rows.get(i);
        return ans;
    }
}

一些知识点:
这里使用到了ArrayList,其常用的函数参考ArrayList常见方法
在这里插入图片描述

7.整数反转

思路
该题的解题思路是将给定的数字依次取出其各位数字,并依次推入新的数字中,其中取出个位数字使用取余操作,比较重要的一点是对溢出的判断,判断条件如下图右边部分推出,详细的可以参考视频讲解-整数反转
在这里插入图片描述
时间复杂度
时间复杂度为O(n)nx的长度。
代码实现

class Solution {
    public int reverse(int x) {
        int ans = 0;
        while(x!=0){
            if(x > 0 && ans > (Integer.MAX_VALUE - x % 10) / 10) return 0;
            if(x < 0 && ans < (Integer.MIN_VALUE - x % 10) / 10) return 0;
            ans = ans * 10 + x % 10;
            x /= 10;
        }
        return ans;
    }
}

8.字符串转换整数(atoi)

思路
首先去掉空格,然后设置sign来确定正负号,如果有“-”,则sign=-1,反之为1,去掉空格并确定正负号后将字符串中的连续数字加入结果中,表达式为ans = ans * 10 + sign * (charArry[i]-'0'),最后需要进行溢出判断,如果大于最大值或者小于最小值,则直接输出最大值和最小值。详细的视频讲解点击视频讲解-字符串转换整数
在这里插入图片描述
时间复杂度:
时间复杂度为O(n)
代码实现:

class Solution {
    public int myAtoi(String s) {
        int ans = 0;
        //全是空格
        int start = 0;
        char[] charArry = s.toCharArray();
        while(start<s.length() && charArry[start] == ' ') start++;
        if(start == s.length()) return 0;
        //提取正负号
        int sign = 1;
        if(charArry[start] == '-'){
            sign = -1;
            start++;
        }else if(charArry[start] == '+') start++;

        for(int i=start;i<s.length() && Character.isDigit(charArry[i]);i++){
            ans = ans * 10 + sign * (charArry[i]-'0');
            if(ans > 0 && ans >= Integer.MAX_VALUE / 10) return Integer.MAX_VALUE;
            if(ans < 0 && ans <= Integer.MIN_VALUE / 10) return Integer.MIN_VALUE;
        }
        return ans;
    }
}

注:溢出判断存在问题,有一些测试用例没有通过,目前还没找到解决办法,如果有小伙伴有好的方法,欢迎留言讨论。
更新:来更新啦,第8题有一部分测试用例没有通过,本来我以为是边界判断出错了,但其实是数据类型的问题,之前在定义ans是使用的是int数据类型,后来为了溢出换成了long类型,但是发现运行会报错,所以就又改回int,这也是部分用例未通过的原因,正确的做法是将ans定义成为long类型,报错的原因是long类型的ans不能作为int类型返回,必须要强转为int类型。即return那要改为return (int)ans==ans?(int)ans:0;
更新代码:

class Solution {
    public int myAtoi(String s) {
        long ans = 0;
        //全是空格
        int start = 0;
        char[] charArry = s.toCharArray();
        while(start<s.length() && charArry[start] == ' ') start++;
        if(start == s.length()) return 0;
        //提取正负号
        int sign = 1;
        if(charArry[start] == '-'){
            sign = -1;
            start++;
        }else if(charArry[start] == '+') start++;

        for(int i=start;i<s.length() && Character.isDigit(charArry[i]);i++){
            ans = ans * 10 + sign * (charArry[i]-'0');
            if(ans > 0 && ans >= Integer.MAX_VALUE) return Integer.MAX_VALUE;
            if(ans < 0 && ans <= Integer.MIN_VALUE) return Integer.MIN_VALUE;
        }
        return (int)ans==ans?(int)ans:0;
    }
}

注:需要补充的是,如果对于每题的思路不是很理解,可以点击链接查看视频讲解,是我在B站发现的一个宝藏UP主,视频讲解很清晰(UP主用的是C++),可以结合视频参考本文的java代码。
待续…

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘子味的小橙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值