LeetCode-hot100题解—Day3

原题链接:力扣热题-HOT100
题解的顺序和题目的顺序一致,那么接下来就开始刷题之旅吧~
1-8题见LeetCode-hot100题解—Day1
9-16题见LeetCode-hot100题解—Day2
注:需要补充的是,如果对于每题的思路不是很理解,可以点击链接查看视频讲解,是我在B站发现的一个宝藏UP主,视频讲解很清晰(UP主用的是C++),可以结合视频参考本文的java代码。

17.电话号码的字母组合

思路
本题采用深度优先搜索来解决,可以参考下图,以23为例,进行深度优先搜索,我们将每个数字代表的字符串定义到数组strs中,开始深度优先搜索,每次当idx == digits.length()时,说明已经搜索到了digits串最后一个数字,即为一个结果,加入ans中,否则取出digits中下标为idx的数字所对应的strs中的字符串,进行遍历,将对应下标的字符加入combine中,并递归调用dfs,实现深度优先搜索,注意搜索后要将combine中最后一个字符弹出,进行新一轮的搜索(如得到ad后弹出d,继续搜索可得到ae,依次类推…),详细讲解参考视频讲解-电话号码的字母组合
在这里插入图片描述
时间复杂度
时间复杂度为O(4^n),其中n是输入字符串digits的长度。代码中使用了深度优先搜索算法,通过递归遍历digits中的每个数字对应的字符集合,然后进行组合生成结果。每个数字对应的字符集合的大小为4,所以总共有4^n种可能的组合。在遍历每个数字对应的字符集合时,需要进行递归调用dfs函数,因此总共需要进行n次递归调用。所以总的时间复杂度为O(4^n)
代码实现

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> ans = new ArrayList<>();
        String[] strs = new String[]{
            "","","abc","def",
            "ghi","jkl","mno",
            "pqrs","tuv","wxyz"
        };
        //如果传入的串是空的,直接返回结果,即空串
        if(digits.length() == 0) return ans;
        //由于方便进行组合需要对结果字符串尾部进行插入和删除元素操作,所以使用StringBulider更方便
        StringBuilder combine = new StringBuilder("");
        //调用函数
        dfs(digits,0,combine,strs,ans);
        return ans;
    }

    //定义搜索函数
    private void dfs(String digits,int idx,StringBuilder combine,String[] strs,List<String> ans){
        if(idx == digits.length()){
            ans.add(combine.toString());
            return ;
        }
        String s = strs[digits.charAt(idx)-'0'];
        for(int i = 0;i < s.length();i++){
            combine.append(s.charAt(i));
            dfs(digits,idx+1,combine,strs,ans);
            combine.deleteCharAt(combine.length() - 1);
        }
    }
}

知识拓展StringBuilder的使用
在本题中用到了StringBuilder,如果要对某个字符串进行尾部插入或者删除一个字符的操作,则可以使用StringBuilder,用法如下:

//插入一个元素
StringBuilder sb = new StringBuilder("Hello");
char characterToAdd = '!';
sb.append(characterToAdd);
//注意,需要将sb使用toString()函数转换为字符串
String newStr = sb.toString();
System.out.println(newStr);  // 输出 "Hello!"
//删除一个元素
StringBuilder sb = new StringBuilder("Hello!");
sb.deleteCharAt(sb.length() - 1);
String newStr = sb.toString();
System.out.println(newStr);  // 输出 "Hello"

18.四数之和

思路
这道题的思路和之前第15题三数之和的思路一样,只要在三元组求解的基础上再枚举一下第四个数的值,具体的思路可以参考第15题,视频讲解点击视频讲解-四数之和
时间复杂度
时间复杂度为O(n^3),其中n是数组nums的长度。代码使用了两个嵌套的for循环,每个循环的时间复杂度都为O(n)。在内部的while循环中,lr指针分别从数组的两端向中间移动,最坏情况下需要移动n次。因此,总体的时间复杂度为O(n^3)
代码实现

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(nums);

        for(int i=0;i<nums.length;i++){
            if(i > 0 && nums[i] ==nums[i-1]) continue;
            for(int j=i+1;j<nums.length;j++){
                if(j>i+1 && nums[j] == nums[j-1]) continue;
                int l = j + 1;
                int r = nums.length - 1;
                long sum = (long)target - (long)nums[i] - (long)nums[j];
                while(l < r){
                    if((long)nums[l] + (long)nums[r] == sum){
                        ans.add(Arrays.asList(nums[i],nums[j],nums[l],nums[r]));
                        while(l < r && nums[l] == nums[l+1]) l++;
                        while(l < r && nums[r] == nums[r-1]) r--;
                        l++;
                        r--;
                    }else if((long)nums[l] + (long)nums[r] < sum){
                        l++;
                    }else{
                        r--;
                    }
                }
            }
        }
        return ans;
    }
}

注意:

long sum = (long)target - (long)nums[i] - (long)nums[j];

这样写是因为没有考虑到目标值 target 可能超过了 Integer.MAX_VALUE,它的取值范围为 [-2^31, 2^31 - 1]。当 target 的值超过 Integer.MAX_VALUE 时,计算 sum = target - nums[i] - nums[j] 时可能会溢出。为了解决这个问题,可以将 target 的类型更改为 long,以便支持更大的值。然后在计算 sum 时,也将 nums[i] 和 nums[j] 转换为 long 类型,避免溢出。否则会报下面的错误,不能通过全部的测试用例。在这里插入图片描述

19.删除链表的倒数第N个结点

思路
本题采用快慢指针来解决,让快指针比慢指针先走n步,当快指针到达链表尾部时,慢指针所指向的下一个结点就是需要删除的倒数第n个结点,在做链表相关的题目时一般需要在头节点的前面设置一个虚拟的结点,方便边界的判断。详细的视频讲解点击视频讲解-删除链表的倒数第N个结点
在这里插入图片描述

时间复杂度
时间复杂度为O(n),其中n是链表的长度。代码中有一个循环,循环的次数是链表的长度n,因此时间复杂度为O(n)
代码实现

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        ListNode slow = dummy;
        ListNode fast = dummy;
        dummy.next = head;
        //让快指针先向前走n步
        for(int i = 0;i < n;i++){
            fast=fast.next;
        }
        while(fast.next != null){
            slow = slow.next;
            fast = fast.next;
        }
        //删除慢指针的下一个结点,即需要删除的结点
        slow.next = slow.next.next;
        return dummy.next;
    }
}

20.有效的括号

思路
本题采用栈结构,先将对应的左右括号用哈希表存储,其中key是左括号,value是右括号,然后遍历字符串,每次遇到左括号('(','{','[')则将其对应的右括号压入栈中,当遍历到最近的右括号与栈顶元素匹配则弹出栈顶元素,表示之前的左括号得到了匹配(左括号会和离其最近的右括号配对),最后当栈为空时说明每个左括号都得到匹配。视频讲解点击视频讲解-有效的括号
时间复杂度
时间复杂度为O(n),其中n是字符串s的长度。
代码实现

class Solution {
    public boolean isValid(String s) {
        HashMap<Character,Character> pairs = new HashMap<>();
        pairs.put('(',')');
        pairs.put('{','}');
        pairs.put('[',']');

        Stack<Character> stk = new Stack<>();
        for(int i = 0;i < s.length();i++){
            //判断是否存在左括号,若存在则将其对应的右括号入栈
            if(pairs.containsKey(s.charAt(i))){
                stk.push(pairs.get(s.charAt(i)));
            }else{
                //如果栈为空或者栈顶元素不是匹配的右括号,则不是有效的
                if(stk.isEmpty() || stk.peek() != s.charAt(i)){
                    return false;
                }
                //否则弹出栈顶元素
                stk.pop();
            }
        }
        return stk.isEmpty();
    }
}

知识拓展JavaStack的用法
Java中,可以使用Stack<Character>来声明和创建一个字符栈,下面的示例中总结了Stack的常见的用法。

public class Main {
    public static void main(String[] args) {
        // 创建一个字符栈
        Stack<Character> stk = new Stack<>();

        // 在栈顶添加元素
        stk.push('a');
        stk.push('b');
        stk.push('c');

        // 从栈顶弹出元素
        char poppedChar = stk.pop();
        System.out.println("Popped char: " + poppedChar);

        // 获取栈顶元素但不弹出
        char topChar = stk.peek();
        System.out.println("Top char: " + topChar);

        // 检查栈是否为空
        boolean isEmpty = stk.isEmpty();
        System.out.println("Is stack empty? " + isEmpty);

        // 获取栈的大小
        int size = stk.size();
        System.out.println("Stack size: " + size);
    }
}

21.合并两个有序链表

思路
本题采用双指针来解决,分别遍历两个链表的结点,比较结点的大小,将结点较小的加入到新的链表中并将其指针后移,最后如果两个链表不一样长,遍历结束短的链表后会退出循环,把剩下的链表后面的结点直接加到新链表中即可。详细的讲解点击视频讲解-合并两个有序链表
在这里插入图片描述

时间复杂度
时间复杂度为O(n + m),其中nm分别是list1list2的长度。代码中使用了两个指针list1list2来遍历两个链表,只要两个指针都没有遍历完,就会进行比较并将较小的值添加到新链表中。
在最坏的情况下,需要遍历两个链表的全部节点。因此,时间复杂度为O(n + m)。这是因为无论链表的长度如何,每个节点只会被访问一次。
代码实现

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        while(list1 != null && list2 != null){
            if(list1.val <= list2.val){
                cur.next = new ListNode(list1.val);
                list1 = list1.next;
            }else{
                cur.next = new ListNode(list2.val);
                list2 = list2.next;
            }
            cur = cur.next;
        }
        //遍历结束需要将剩下的那个链表元素全部加到新链表
        cur.next = list1 != null ? list1 : list2;
        return dummy.next;
    }
}

22.括号生成

思路
本题需要用到判断括号串是否有效的充要条件:
规则1:任意前缀"(“数量>=”)"数量(是指在括号串中随便画一条线,前面部分左括号数量一定大于等于右括号数量,等于的情况是画线的前边是整个括号串)
规则2:左右括号数量相等
结合以上两个条件采用深度优先搜索,在不同的位置放左右括号结合两个规则进行有效性的判断得到结果集。视频讲解点击视频讲解-括号生成

              ""
           /       \
       "("         ")"
     /     \      /     \
   "(("    "()"  "()"   "))"
   /   \    /  \
"(((" "(()" "()" "))"
 ...

时间复杂度
时间复杂度为O(2^(2n)),调用了dfs函数进行深度优先搜索,dfs函数的时间复杂度取决于递归的次数,每个递归的位置都有2种可能,共有2n个这样的位置。
代码实现

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        dfs(0,0,n,"",ans);
        return ans;
    }

    //深度优先函数的实现
    //这里需要使用到判断有效括号的充要条件,只要满足以下两点的括号串即为有效的
    //规则1:任意前缀"("数量>=")"数量(是指在括号串中随便画一条线,前面部分左括号数量一定大于等于右括号数量,等于的情况是画线的前边是整个括号串)
    //规则2:左右括号数量相等
    private void dfs(int lc,int rc,int n,String seq,List<String> ans){
        //当左右括号的数量等于n时即为结果的一种,加入结果集
        if(lc == n && rc == n){
            ans.add(seq);
            return ;
        }
        //表示一定还存在左括号
        if(lc < n) dfs(lc+1,rc,n,seq+"(",ans);
        //表示还剩余右括号并且左括号数量大于右括号数量,参见规则1
        if(rc < n && lc > rc) dfs(lc,rc+1,n,seq+")",ans);
    }
}

23.合并K个升序链表

思路
本题采用较为简单的暴力的解法,首先我们将这K个链表中的元素全部加入到一个动态数组nums,然后对数组nums进行排序,再将数组的元素的依次加入新链表中,最后返回新链表的头节点即可。还有一种时间复杂度较低的方法采用小根堆。详细的讲解点击视频讲解-合并K个升序链表
时间复杂度
时间复杂度是O(NlogN),其中N是所有链表中节点的总数。以下是时间复杂度的分析:

  1. 首先,我们遍历所有的链表,将所有节点的值添加到一个列表中。这个过程的时间复杂度是O(N),其中N是所有链表中节点的总数。
  2. 然后,我们对这个列表进行排序,排序的时间复杂度是O(NlogN)。这取决于使用的排序算法,通常情况下使用的是快速排序或归并排序。
  3. 最后,我们将排序后的列表重新构建为一个新的链表。这个过程的时间复杂度是O(N),其中N是排序后的列表的长度。
    因此,总的时间复杂度是O(N + NlogN + N),即O(NlogN)。
    代码实现
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        List<Integer> nums = new ArrayList<>();

        for(ListNode list : lists){
            while(list != null){
                nums.add(list.val);
                list = list.next;
            }
        }
        Collections.sort(nums);

        ListNode cur = new ListNode(-1);
        ListNode head = cur;
        for(int num : nums){
            cur.next = new ListNode(num);
            cur = cur.next;
        }
    return head.next;
    }
}

知识拓展:关于Arrays.sort()Collections.sort()的使用
Arrays.sort()Collections.sort()方法之间的选择基于数据类型的可比较性。
Arrays.sort()方法适用于对数组进行排序,而且数组的元素类型必须实现了Comparable接口,即具备可比较性。比如,整数类型int[]Integer[]和字符串类型String[]都是可以使用Arrays.sort()进行排序的。
Collections.sort()方法适用于对集合类进行排序,集合类中的元素类型也必须实现了Comparable接口。比如,ArrayList<Integer>LinkedList<String>等都是可以使用Collections.sort()进行排序的。
所以,无论数组的长度是确定的还是动态的,关键是要确定数组元素的类型是否具备可比较性。如果数组元素类型实现了Comparable接口,可以使用Arrays.sort()方法进行排序;如果使用的是集合类并且集合元素类型实现了Comparable接口,可以使用Collections.sort()方法进行排序。

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

思路
本题采用三指针来解决,首先需要新建一个虚拟头节点,方便操作,然后分别新建三个指针指向虚拟头节点和头节点以及头节点的下一个节点,通过操作三个指针使得节点互换位置,然后将指针整体后移,相同的操作。详细的讲解点击视频讲解-两两交换链表中的节点
在这里插入图片描述

时间复杂度
时间复杂度为O(n),其中n是链表的长度。
代码实现

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        //定义两个指针分别指向虚拟头节点dummy和其下一个节点
        ListNode pre = dummy;
        ListNode cur = dummy.next;
        while(cur != null && cur.next != null){
            //定义第三个指针
            ListNode next = cur.next;
            pre.next = next;
            cur.next = next.next;
            next.next = cur;
            pre = cur;
            cur = cur.next;
        }
        return dummy.next;
    }
}

待续…

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
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乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘子味的小橙

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

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

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

打赏作者

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

抵扣说明:

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

余额充值