文章目录
【LeetCode热题100】打开第11天
⛅前言
大家好,我是知识汲取者,欢迎来到我的LeetCode热题100刷题专栏!
精选 100 道力扣(LeetCode)上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,熟练掌握这 100 道题,你就已经具备了在代码世界通行的基本能力。在此专栏中,我们将会涵盖各种类型的算法题目,包括但不限于数组、链表、树、字典树、图、排序、搜索、动态规划等等,并会提供详细的解题思路以及Java代码实现。如果你也想刷题,不断提升自己,就请加入我们吧!QQ群号:827302436。我们共同监督打卡,一起学习,一起进步。
博客主页💖:知识汲取者的博客
LeetCode热题100专栏🚀:LeetCode热题100
Gitee地址📁:知识汲取者 (aghp) - Gitee.com
Github地址📁:Chinafrfq · GitHub
题目来源📢:LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台
PS:作者水平有限,如有错误或描述不当的地方,恳请及时告诉作者,作者将不胜感激
🔒有效括号
🔑题解
-
解法一:替换
/** * @author ghp * @title 有效的括号 */ class Solution { public boolean isValid(String s) { if (s.length() % 2 == 1) { // 字符个数为奇数,一定含有无效括号 return false; } // 循环替换所有的括号,直到无法替换为止 while (true) { int len = s.length(); s = s.replace("()", ""); s = s.replace("{}", ""); s = s.replace("[]", ""); if (s.length() == len) { // 已经将所有可替换的全都替换了,如果此时len为0说明字符串有效 return len == 0; } } } }
复杂度分析:
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),while循环每一次都替换一对括号,总共需要循环n/2次,每次替换
replace
方法的时间复杂度是n,所以总的时间复杂度为 O ( n / 2 ∗ n ) O(n/2*n) O(n/2∗n) - 空间复杂度: O ( 1 ) O(1) O(1)
其中 n n n 为括号的个数
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),while循环每一次都替换一对括号,总共需要循环n/2次,每次替换
-
解法二:栈
这个虽然时间复杂度
import java.util.HashMap; import java.util.Map; import java.util.Stack; /** * @author ghp * @title 有效的括号 */ class Solution { public static final Map<Character, Character> brackets = new HashMap() {{ put('(', ')'); put('[', ']'); put('{', '}'); }}; public boolean isValid(String s) { if (s.length() % 2 == 1) { // 字符个数为奇数,一定含有无效括号 return false; } Stack<Character> stack = new Stack<>(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (brackets.containsKey(ch)) { // 左括号,直接入栈 stack.push(ch); } else { // 右括号,出栈,并判断是否与当前括号匹配(注意这里要判空,防止NPE) if (stack.isEmpty() || brackets.get(stack.pop()) != ch) { return false; } } } return stack.isEmpty(); } }
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
其中 n n n 为字符的个数
🔒合并两个有序链表
🔑题解
💡思路一:合并到第三方链表上
创建一个新的头节点,然后构成一个新链表,这个链表用来存放链表1和链表2比较留下的最小的结点
🔐【非递归实现】
解题示意图:
原始代码:
import java.util.*;
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null || list2 == null) {
//只要有一个为空就直接返回另一个
return list1 != null ? list1 : list2;
}
ListNode head1 = list1;//辅助遍历list1
ListNode head2 = list2;//辅助遍历list2
int val;//用来创建新链表的头节点
//选出链表1和链表链表2较小的头节点作为 新链表的头节点的val
if(head1.val < head2.val) {
val = head1.val;
head1 = head1.next;//这里需要后移,因为已经比较过了,后面就不需要再进行比较了
}else {
val = head2.val;
head2 = head2.next;
}
ListNode newNode = new ListNode(val);//新建一个头节点,他是合并后链表的头节点
ListNode head = newNode;//指向newNode链表,辅助遍历
while(head1 != null && head2 != null) {//切记判断不要使用head1.next或者head2.next
if(head1.val < head2.val) {
//说明当前链表1的结点是最小的
head.next = head1;//将链表1的结点添加到新链表上
head = head.next;//始终保证辅助指针再新链表的尾结点,方便结点的添加
head1 = head1.next;
}else {
head.next = head2;
head = head.next;
head2 = head2.next;
}
}
if(head1 != null && head2 == null) {
//直接将链表1不为空的部分连接到新链表后面
head.next = head1;
}
if(head1 == null && head2 != null) {
直接将链表2不为空的部分连接到新链表后面
head.next = head2;
}
return newNode;
}
}
期间犯的错去:新建结点直接ListNode newNode = null;
导致报空指针异常,原因是为空时,后面用到了head.next = head1|head2,后面还会讲解(当然还有一些睁眼瞎的错误,就不记录了w(゚Д゚)w)
代码优化:
- 优化1:时间优化。我们可以直接创建一个结点,然后返回的时候只返回newNode.next结点即可,这样就不需要先进行判断了是用链表1的结点作为头节点,还是链表2的结点作为头节点,能够一定程度降低时间损耗。
- 优化2:空间优化。同时因为我们是将链表1和链表2进行合并,不再需要再使用链表1和链表2,所以可以直接使用list1和list2进行遍历,而不再借助辅助指针进行比遍历,能一定程度减少内存消耗
优化后的代码:
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null || list2 == null) {
//只要有一个为空就直接返回另一个
return list1 != null ? list1 : list2;
}
ListNode newNode = new ListNode(-1);//新建一个空指针,指向合并后链表的头节点
ListNode head = newNode;//辅助newNode,用新增结点
while(list1 != null && list2 != null) {
if(list1.val < list2.val) {
head.next = list1;
head = head.next;
list1 = list1.next;
}else {
head.next = list2;
head = head.next;
list2 = list2.next;
}
}
if(list1 != null && list2 == null) {
//直接将链表1不为空的部分连接到新链表后面
head.next = list1;
}
if(list1 == null && list2 != null) {
直接将链表2不为空的部分连接到新链表后面
head.next = list2;
}
return newNode.next;
}
}
🔐【递归实现】
解题示意图:
代码实现:
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null || list2 == null) {
//只要有一个为空就直接返回另一个
return list1 != null ? list1 : list2;
}
ListNode newNode = new ListNode(-1);//新建头节点,用于构建新链表
ListNode head = newNode;//辅助指针,用于遍历新链表同时新增结点
if(list2.val>list1.val){
head.next = list1;//将链表1的结点挂到新链表上
head = head.next;//始终保证head指向新链表的尾结点,方便增加结点
head.next = Merge(list1.next,list2);//list1已经比完,移动list1指针
}
else{
head.next = list2;
head = head.next;
head.next = Merge(list1,list2.next);
}
return newNode.next;
}
}
💡思路二 :合并到一个链表上
先确定头节点是选用list1还是list2,然后借助辅助指针,将链表1和链表2串连起来
🔐【非递归实现】
解题示意图:
代码实现:
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null || list2 == null) {
//只要有一个为空就直接返回另一个
return list1 != null ? list1 : list2;
}
ListNode newNode = null;//新建一个空指针,指向合并后链表的头节点
ListNode head = null;//辅助newNode,用新增结点
while(list1 != null && list2 != null) {
if(list1.val < list2.val) {
if(newNode == null) {
//以head1为头结点
newNode = list1;
head = newNode;//设置辅助指针,因为头指针是新链表的位置标识,不能动
list1 = list1.next;
continue;//后面的代码就不需要执行了
}
head.next = list1;
head = head.next;
list1 = list1.next;
}else {
if(newNode == null) {
//以head2为头节点
newNode = list2;
head = newNode;
list2 = list2.next;
continue;
}
head.next = list2;
head = head.next;
list2 = list2.next;
}
}
if(list1 != null && list2 == null) {
//直接将链表1不为空的部分连接到新链表后面
head.next = list1;
}
if(list1 == null && list2 != null) {
直接将链表2不为空的部分连接到新链表后面
head.next = list2;
}
return newNode;
}
}
🔐【递归实现】
代码实现:
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1 == null || list2 == null) {
return list1 != null ? list1 : list2;//只要有一个为空就直接返回另一个
}
//判断选用哪一个作为头节点
if(list2.val>list1.val){
//开始递归
list1.next = Merge(list1.next,list2);
return list1;
}
else{
list2.next = Merge(list1,list2.next);
return list2;
}
}
}
这个是参考别人的,这段代码太妙了wow~ ⊙o⊙
具体实现思路,就是不断地去寻找两个链表的最小结点,直到为空为止(具体过程我知道,但是不知道为什么描述不出来,可能是还没完全懂吧┭┮﹏┭┮)
递归需要两个必备条件:递归终止条件
+递归入口条件
。即什么时候结束递归,什么时候开始递归。明白这两点应该就能看懂这段代码了