文章目录
21. 合并两个有序链表
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
题解:
思路:
我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,
将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
算法:
首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。
我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :
如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。
不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。在循环终止的时候, l1 和 l2 至多有一个是非空的。
由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。
这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
c++版
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode * l = new ListNode(-1);
ListNode * p = l;
while(l1 and l2){
if(l1->val < l2->val){
p->next = l1;
p = p->next;
l1 = l1->next;
}
else{
p->next = l2;
p = p->next;
l2 = l2->next;
}
}
if(l1)p = p->next = l1,l1 = l1->next;
if(l2)p = p->next = l2,l2 = l2->next;
return l->next;
}
};
python版
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
l = ListNode(-1);
p = l
while(l1 or l2):
if l1 == None:
p.next = ListNode(l2.val)
p = p.next
l2 = l2.next
elif l2 == None:
p.next = ListNode(l1.val)
p = p.next
l1 = l1.next
else:
if l1.val < l2.val:
p.next = ListNode(l1.val)
p = p.next
l1 = l1.next
else:
p.next = ListNode(l2.val)
p = p.next
l2 = l2.next
return l.next
22. 括号生成
题目描述
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
题解:
递归:
1.使用递归。
2.每次可以放置左括号的条件是当前左括号的数目不超过 n。
3.每次可以放置右括号的条件是当前右括号的数目不超过左括号的数目。
c++版
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
dfs(n, 0, 0, "");
return ans;
}
void dfs(int n, int l, int r, string sep = ""){
if(l == n && r == n){
ans.push_back(sep);
}
else{
if(l < n) dfs(n, l + 1, r, sep + "(");
if(r < n && l > r) dfs(n, l, r + 1, sep + ")");
}
}
};
python版
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
ans = []
def dfs(n, l, r, s):
if l == n and r == n:
ans.append(s)
else:
if l < n:
dfs(n, l + 1, r, s + '(')
if r < n and l > r:
dfs(n, l, r + 1, s + ')')
dfs(n, 0, 0, "")
return ans
23. 合并K个排序链表
题目描述
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
题解:
使用优先队列合并:
我们需要维护当前每个链表没有被合并的元素的最前面一个,k 个链表就最多有 k 个满足这样条件的元素,
每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。
c++版
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
struct cmp{
bool operator()(ListNode* a, ListNode* b){
return a->val > b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,cmp> heap;
ListNode* dummy = new ListNode(-1);
ListNode* tail = dummy;
for(auto l : lists) if(l) heap.push(l);
while(heap.size()){
auto t = heap.top();
heap.pop();
tail = tail->next = new ListNode(t->val);
t = t->next;
if(t) heap.push(t);
}
return dummy->next;
}
};
python版
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
import heapq
heap = []
for node in lists:
while node:
heappush(heap, node.val)
node = node.next
dummy = ListNode(-1)
p = dummy
while heap:
p.next = ListNode(heappop(heap))
p = p.next
return dummy.next
24. 两两交换链表中的节点
题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
题解:
我们把链表分为两部分,即奇数节点为一部分,偶数节点为一部分,A 指的是交换节点中的前面的节点,
B 指的是要交换节点中的后面的节点。在完成它们的交换,我们还得用 prevNode 记录 A 的前驱节点。
算法:
1.firstNode(即 A) 和 secondNode(即 B) 分别遍历偶数节点和奇数节点,即两步看作一步。
2.交换两个节点:firstNode.next = secondNode.next; secondNode.next = firstNode
3.还需要更新 prevNode.next 指向交换后的头:prevNode.next = secondNode
4.迭代完成后得到最终的交换结果。
c++版
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* p = dummy;
while(p->next && p->next->next){
auto a = p->next;
auto b = a->next;
p->next = b;
a->next = b->next;
b->next = a;
p = a;
}
return dummy->next;
}
};
python版
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy = ListNode(-1)
dummy.next = head
p = dummy
while p.next and p.next.next:
a = p.next
b = a.next
p.next = b
a.next = b.next
b.next = a
p = a
return dummy.next
25. K 个一组翻转链表
题目描述
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
- 你的算法只能使用常数的额外空间。
- 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
题解:
(模拟) O(L):
1.增加虚拟头结点 dummy,并且令 cur 指针指向 dummy。
2.对于每一轮的修改,令 first 指针为 cur->next,并求出 end 指针为下一轮需要交换的最后一个结点;
在找 end 的过程中,若不足 k 个结点,则直接终止循环。
3.在找到 first 和 end 后,设置 p1 和 p2 两个指针修改相邻结点之间的连接关系,需要一个临时的 new_p2 指针。
4. 最终修改 cur->next 和 first->next。
5.令 cur 指向下一轮修改的起始位置的前一个位置。
c++版
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* nummy = new ListNode(-1);
nummy->next = head;
for(auto p = nummy;;){
auto q = p;
for(int i = 0; i < k; i++){
q = q->next;
if(!q) break;
}
if(!q) break;
auto a = p->next;
auto b = a->next;
for(int i = 0; i < k - 1; i++){
auto c = b->next;
b->next = a;
a = b, b = c;
}
auto c = p->next;
p->next = a, c->next = b;
p = c;
}
return nummy->next;
}
};
python版
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
dummy = ListNode(-1)
dummy.next = head
q = dummy
while(q):
p = q
for i in range(0, k):
p = p.next
if p == None:
break
if p == None:
break
a = q.next
b = a.next
for i in range(0, k - 1):
c = b.next
b.next = a
a = b
b = c
c = q.next
q.next = a
c.next = b
q = c
return dummy.next
26. 删除排序数组中的重复项
题目描述
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
题解:
双指针法算法:
数组完成排序后,我们可以放置两个指针 i 和 j,其中 i 是慢指针,而 j 是快指针。只要 nums[i] = nums[j],我们就增加 j 以跳过重复项。
当我们遇到 nums[j] 不等于 nums[i] 时,跳过重复项的运行已经结束,因此我们必须把它(nums[j])的值复制到 nums[i + 1]。
然后递增 i,接着我们将再次重复相同的过程,直到 j 到达数组的末尾为止。
c++版
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(!nums.size()) return 0;
int l = 0, r = 1;
for(; r < nums.size(); r++){
if(nums[l] != nums[r]){
l++;
nums[l] = nums[r];
}
}
return l + 1;
}
};
python版
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
l = 0
for r in range(1,len(nums)):
if nums[l] != nums[r]:
l += 1
nums[l] = nums[r]
return l + 1
27. 移除元素
题目描述
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
题解:
双指针算法:
当 nums[j] 与给定的值相等时,递增 j 以跳过该元素。只要 nums[j] 不等于 val 我们就复制 nums[j] 到 nums[i] 并同时递增两个索引。
重复这一过程,直到 j 到达数组的末尾,该数组的新长度为 i。
c++版
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int l = 0, r = 0;
for(; r < nums.size(); r++){
if(nums[r] != val) nums[l++] = nums[r];
}
return l;
}
};
python版
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
l = 0
for r in range(0, len(nums)):
if nums[r] != val:
nums[l] = nums[r]
l += 1
return l
28. 实现 strStr()
题目描述
题目描述
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = “hello”, needle = “ll”
输出: 2
示例 2:
输入: haystack = “aaaaa”, needle = “bba”
输出: -1
题解:
KMP 算法 O(n+m):
1.创建模式串的 next 数组,为当前失配后,下一个可以比较的位置。
2.在待匹配串上应用 next 数组。
c++版
class Solution {
public:
int strStr(string haystack, string needle) {
if(needle.size() == 0) return 0;
int n = haystack.size(), m = needle.size();
haystack = ' ' + haystack;
needle = ' ' + needle;
vector<int> next(m + 1);
for(int i = 2, j = 0; i <= m; i++){
while(j && needle[i] != needle[j + 1])j = next[j];
if(needle[i] == needle[j + 1])j++;
next[i] = j;
}
for(int i = 1, j = 0; i <= n; i++){
while(j && haystack[i] != needle[j + 1])j = next[j];
if(haystack[i] == needle[j + 1])j++;
if(j == m)return i - m;
}
return -1;
}
};
python版
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
n = len(haystack)
m = len(needle)
next = [0 for _ in range(m + 1)]
haystack = ' ' + haystack
needle = ' ' + needle
j = 0
for i in range(2, m + 1):
while j and needle[i] != needle[j + 1]:
j = next[j]
if needle[i] == needle[j + 1]:
j += 1
next[i] = j
j = 0
for i in range(1, n + 1):
while j and haystack[i] != needle[j + 1]:
j = next[j]
if haystack[i] == needle[j + 1]:
j += 1
if j == m:
return i - m
return -1
29. 两数相除
题目描述
给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
示例 1:
输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = truncate(3.33333…) = truncate(3) = 3
示例 2:
输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = truncate(-2.33333…) = -2
提示:
- 被除数和除数均为 32 位有符号整数。
- 除数不为 0。
- 假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−2 ^ 31, 2 ^ 31 − 1]。本题中,如果除法结果溢出,则返回 2 ^ 31 − 1。
题解:
倍增算法:
1.让被除数不断减去除数,直到不够减。
2.每次减完后除数翻倍,并记录当前为初始除数的几倍(用 t 表示倍数 time),
3.若发现不够减且 t 不为 1 则让除数变为原来的一半, t 也减半
4.a 为被除数绝对值,b 为除数绝对值,r 表示 result,t 表示当前除数对于原始除数的倍数
a << b 相当于 a * 2b,a >> b 相当于 a // 2b
4.异或操作 ^ 可以判断俩数字是否异号
python版
class Solution:
def divide(self, dividend: int, divisor: int) -> int:
a, b, t, r = abs(dividend), abs(divisor), 1, 0
while a >= b or t > 1:
if a >= b: r += t; a -= b; t += t; b += b
else: t >>= 1; b >>= 1
r = r if dividend ^ divisor >= 0 else -r
return min(r, (1 << 31) - 1)
c++版
class Solution {
public:
int divide(int dividend, int divisor) {
if(dividend == INT_MIN && divisor == -1)
return INT_MAX;
int a = abs(dividend);
long long int b = abs(divisor);
long long int t = 1;
long long int r = 0;
while(a >= b || t > 1){
if(a >= b) r += t, a -= b, t += t, b += b;
else t >>= 1, b >>= 1;
}
if((dividend ^ divisor) < 0) r = -r;
return r;
}
};
30. 串联所有单词的子串
题目描述
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoo” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]
题解:
因为单词长度固定的,我们可以计算出截取字符串的单词个数是否和 words 里相等,所以我们可以借用哈希表。
一个是哈希表是 words,一个哈希表是截取的字符串,比较两个哈希是否相等!
因为遍历和比较都是线性的,所以时间复杂度:O(n^2)
c++版
class Solution {
public:
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ans;
if(s.size() == 0 || words.size() == 0)
return ans;
unordered_map<string, int> mp;
int one_word = words[0].size();
int all_len = words.size() * one_word;
int n = s.size();
int m = words.size();
for(int i = 0; i < m; i++)
mp[words[i]]++;
for(int i = 0; i < n - all_len + 1; i++){
string sub_str = s.substr(i, all_len);
unordered_map<string, int> a;
for(int j = 0; j < all_len; j += one_word){
string s1 = sub_str.substr(j, one_word);
if(mp.count(s1) == 0) break;
a[s1]++;
}
if(a == mp) ans.push_back(i);
}
return ans;
}
};
python版
class Solution:
def findSubstring(self, s: str, words: List[str]) -> List[int]:
from collections import Counter
if len(s) == 0 or len(words) == 0:
return []
ans = []
one_word = len(words[0])
all_len = len(words) * one_word
n = len(s)
words = Counter(words)
for i in range(0, n - all_len + 1):
sub_str = s[i:i + all_len]
l = []
for j in range(0, all_len - one_word + 1,one_word):
l.append(sub_str[j:j + one_word])
if Counter(l) == words:
ans.append(i)
return ans