19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
思路:
我们先定义一个虚拟头节点dummy
指向真正的头节点head
,接下来定义两个指针left
和right
,我们假设总共
5
5
5个节点,返回倒数第
2
2
2个节点,我们先让right
,往右走$n $步,原先为dummy
,之后走到了2,接下来两个下标同时右移,当right移到了5的时候,left移到了3,我们接下来就把left->next
删除掉,就是直接让left->next=left->next->next
;具体看实现
/**
* 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* removeNthFromEnd(ListNode* head, int n) {
ListNode *dummy = new ListNode(0, head);
ListNode *left = dummy, *right = dummy;
while(n --)
right = right->next;
//right先走了n步
//当right走到了最后的时候,left走了m - n步,距离最后n
while(right->next)
{
left = left->next;
right = right->next;
}
left->next = left->next->next;
return dummy->next;
}
};
20. 有效的括号 - 力扣(LeetCode)
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入:s = "()"
输出:true
示例 2:
输入:s = "()[]{}"
输出:true
示例 3:
输入:s = "(]"
输出:false
提示:
1 <= s.length <= 104
s
仅由括号'()[]{}'
组成
思路:
看到括号我们就想到可以用栈来实现,当然实际上用栈也是可以用字符串来实现的,这道题目是括号匹配,我们每次碰到左括号就入栈,碰到右括号就看一下是否匹配,匹配就可以接着判断,不匹配就返回false
,其中判断括号是否匹配可以使用一个特殊的技巧,我们知道相邻括号的ASCII
编码就相差不到2括号的ASCII编码如下:
- ‘(’ 的ASCII码为 40
- ‘)’ 的ASCII码为 41
- ‘[’ 的ASCII码为 91
- ‘]’ 的ASCII码为 93
- ‘{’ 的ASCII码为 123
- ‘}’ 的ASCII码为 125
class Solution {
public:
bool isValid(string s) {
string stackStr;
for(auto c : s)
{
if(c == '(' || c == '{' || c == '[')
stackStr += c;
else
{
if(stackStr.size() && abs(stackStr.back() - c) <= 2)
stackStr.pop_back();
else
return false;
}
}
return stackStr.empty();
}
};
class Solution {
public:
bool isValid(string s) {
stack<char> st;
for(auto c : s)
{
if(c == '(' || c == '{' || c == '[')
st.push(c);
else
{
if(st.size() && abs(st.top() - c) <= 2)
st.pop();
else
return false;
}
}
return st.empty();
}
};
21. 合并两个有序链表 - 力扣(LeetCode)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50]
-100 <= Node.val <= 100
l1
和l2
均按 非递减顺序 排列
思路:
这道题最好是不使用额外空间完成,其他的没什么需要注意的了
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 检查特殊情况
if (!l1) return l2;
if (!l2) return l1;
// 确定新链表的头节点
ListNode* head = nullptr;
if (l1->val < l2->val) {
head = l1;
l1 = l1->next;
} else {
head = l2;
l2 = l2->next;
}
// 合并链表
ListNode* current = head;
while (l1 && l2) {
if (l1->val < l2->val) {
current->next = l1;
l1 = l1->next;
} else {
current->next = l2;
l2 = l2->next;
}
current = current->next;
}
// 处理剩余部分
if (l1) {
current->next = l1;
} else {
current->next = l2;
}
return head;
}
};
22. 括号生成 - 力扣(LeetCode)
数字 n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
提示:
1 <= n <= 8
思路:
注意到数据范围,肯定是使用dfs
完成的,括号生成问题还有一个注意的点就是严格保证左括号>=右括号,接下来看代码实现
class Solution {
public:
vector<string> res; // 存储有效括号组合的结果
vector<string> generateParenthesis(int n) {
dfs(n, 0, 0, ""); // 从初始状态开始深度优先搜索
return res; // 返回结果
}
void dfs(int n, int l, int r, string seq)
{
// 当左右括号数都为n时,说明seq为一个有效的括号组合,存储到结果中
if(l == n && r == n)
{
res.push_back(seq);
}
// 如果左括号数小于n,可以加一个左括号
if(l < n) dfs(n, l + 1, r, seq + '(');
// 如果右括号数小于n,可以加一个右括号,但需要保证右括号数小于左括号数
if(r < n && r < l) dfs(n, l, r + 1, seq + ')');
}
};
23. 合并 K 个升序链表 - 力扣(LeetCode)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
思路:
合并k
个链表,我们想到有序元素存放需要堆,所以我们就定义一个堆
/**
* 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;
//定义一个虚拟头结点和尾结点
auto dummy = new ListNode(-1), tail = dummy;
//将每个链表的头结点加入小根堆
for (auto l : lists) if (l) heap.push(l);
while (heap.size()) {
//取出堆顶元素
auto t = heap.top();
heap.pop();
//将堆顶元素加入结果链表
tail = tail->next = t;
//如果堆顶元素还有下一个结点,则将其加入小根堆中
if (t->next) heap.push(t->next);
}
//返回结果链表的头结点
return dummy->next;
}
};
31. 下一个排列 - 力扣(LeetCode)
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3]
,以下这些都可以视作arr
的排列:[1,2,3]
、[1,3,2]
、[3,1,2]
、[2,3,1]
。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3]
的下一个排列是[1,3,2]
。 - 类似地,
arr = [2,3,1]
的下一个排列是[3,1,2]
。 - 而
arr = [3,2,1]
的下一个排列是[1,2,3]
,因为[3,2,1]
不存在一个字典序更大的排列。
给你一个整数数组 nums
,找出 nums
的下一个排列。
必须** 原地 **修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
思路:
- 从数组末尾往前找,找到第一个位置
j
,使得nums[j]
<nums[j + 1]
- 如果不存在这样的
j
,则说明数组是不递增的,直接将数组逆转即可。 - 如果存在这样的
j
,则从末尾找到第一个位置i > j
,使得nums[i]
>nums[j]
。
- 如果不存在这样的
- 交换
nums[i]
与nums[j]
,然后将数组从j + 1
到末尾部分逆转。
时间复杂度:
- 线性遍历数组常数次,时间复杂度为 O ( n ) O(n) O(n)。
空间复杂度:
- 没有使用任何额外的数组空间,故空间复杂度为 O ( 1 ) O(1) O(1)。
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int n = nums.size(), j = -1;
for(int i = n - 2; i >= 0; i --)
{
if(nums[i] < nums[i + 1])
{
j = i;
break;
}
}
if(j == -1)
reverse(nums.begin(), nums.end());
else
{
for (int i = n - 1; i > j; i--)
if (nums[i] > nums[j]) {
swap(nums[i], nums[j]);
break;
}
reverse(nums.begin() + j + 1, nums.end());
}
}
};