刷top-100的题目,最近做了这四道,原题: 138, 5, 15, 98.
15 3Sum
给你一串整数,让你找里面所有和为0的三元组。
思路: 暴力的话三循环,时间复杂度高。可以考虑先求任意两个数的和a
,然后找剩下的数中值为-a
的元素,但是复杂度依旧很高。考虑先确定一个数b
,那么接下来的任务就是找和为-b
的两个数,这个任务可以通过双指针法实现 - 排序然后从头部和尾部向中间靠拢。举个例子
设数组a[9]={-5 0 1 1 2 3 3 4 4}
,求1 ~ 9
中和为5
的二元组:
- 双指针
i=1, j=8
,结果集res
- 若
a[i]+a[j] == t
,则将这个二元组放入res
,并将i++, j++
- 若
a[i]+a[j] < t
,则i++
- 若
a[i]+a[j] > t
,则j++
- 重复上述操作直到
i>=j
- 若
上述过程会产生重复的元组(1,4)
,产生的原因是相同的元素多次计算了,那只需在每次执行i++
或j++
时,跳过相同的元素即可。
代码:
vector<vector<int>> threeSum(vector<int> &nums) {
if (nums.size() < 3) return vector<vector<int>>();
sort(nums.begin(), nums.end());
typedef pair<int, int> pii;
auto findtarget = [](size_t tindex, const vector<int> &nums, vector<pii> &vecpii) -> bool {
int t = -nums[tindex];
size_t i = tindex + 1, j = nums.size() - 1;
while (i < j && i < nums.size() && j < nums.size()) {
const int tmp = nums[i] + nums[j];
if (tmp == t) {
vecpii.push_back(pii(i, j));
while (i + 1 < nums.size() && nums[i + 1] == nums[i])
i++;
while (j > 0 && nums[j - 1] == nums[j])
j--;
i++, j--;
} else if (tmp > t) {
while (j > 0 && nums[j - 1] == nums[j])
j--;
j--;
} else if (tmp < t) {
while (i + 1 < nums.size() && nums[i + 1] == nums[i])
i++;
i++;
}
}
return vecpii.size() > 0;
};
typedef tuple<int, int, int> tiii;
vector<vector<int>> resvec;
for (size_t i = 0; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue;
vector<pii> vecpii;
if (findtarget(i, nums, vecpii)) {
for (auto &&p : vecpii)
resvec.push_back({nums[i], nums[p.first], nums[p.second]});
}
}
return resvec;
}
98 验证二叉搜索树
二叉搜索树: 左儿子比父亲小,右儿子比父亲大。
思路
-
递归
对于父节点root,左子树小于root->val,右子树大于root->val,相当于规定了每个节点的上界与下界。因此对于节点
p
,假设其上界与下界为(mi, mx)
,则左子树上下界为(mi, root->val)
,右子树上下界为(root->val, mx)
-
宽搜
与递归的思想一样,只是使用队列方式迭代,时间复杂度小一丢丢
-
中序遍历
二叉搜索树的中序遍历结果一定有序,基于这个事实,只要在中序遍历的结果中发现了
res[i]>=res[j]
,就说明不是二叉搜索树。可以先遍历在判断,也可以边遍历边判断
5 最长回文子串
回文串: a, aaa, aba, abba, aaaa, babababab
思路: 回文串是由子回文串组成的,基于这个特点,可以考虑递推的方式。
-
单独一个字符一定是回文串
-
两个相同且相连字符也是回文串
-
若s[i~j]
是回文串,且s[i-1]==s[j+1]
,那么s[i-1 ~ j+1]
也是回文串。若
s[i~j]='bbbb', s[i-1]='b'
,显然s[i-1 ~ i,i+1,i+2,...,j]
都是回文串,因此需要考虑的是以s[i]
开头的所有回文串。 -
若以
s[i]
开始的所有回文串长度为0 1 l2 l3 ... lk
,且对于s[i-1]
,有s[i-1]==s[i+lj+1]
,则说明s[i-1 ~ i+lj+1]
组成新的长为lj+2
的回文串。
即设dp[i]=以s[i]开始的所有回文串长度
,则: dp[i-1]={dp[i][k]+2, 若s[i-1]==s[i+dp[i][k]+1]}
. 据此即可确定以任意s[i]
开头能组成的所有回文串,然后确定最长的即可(可以在上述过程中确定)。另外,dp[i]
只与dp[i-1]
有关,因此可以使用长为2的dp数组,轮流使用以减少空间复杂度。
代码:
string longestPalindrome(string s) {
if (s.size() <= 1)
return s;
const int sz = s.size();
vector<int> dp[2] = {{0, 1}, {0, 1}};
int mxlen = 1, mxpos = 0;
for (int i = sz - 2; i >= 0; i--) {
int ia = 0, ib = 1;
if (i & 1) ia = 1, ib = 0;
dp[ia] = {0, 1};
for (auto &&k : dp[ib]) {
if (i + k + 1 < sz && s[i] == s[i + k + 1]) {
dp[ia].push_back(k + 2);
if (k + 2 >= mxlen)
mxlen = k + 2, mxpos = i;
}
}
}
return s.substr(mxpos, mxlen);
}
138 拷贝带有随机指针的链表
拷贝一个链表,该链表的每个节点有一个指针,指向任意一个值。
思路: 拷贝时不考虑随机指针,记录 原指针 -> 新指针
的映射,拷贝之后再次遍历确定随机指针的值,但是这样的空间复杂度较高。如何减小呢?如果原链表可修改,那么可将新的节点插入到原节点后面,即
old: a->b->c->d
new: a->a1->b->b1->c->c1->d->d1
那么随机指针的值就非常容易确定了。方法很有趣,不过指针的操作往往比较繁琐,需要先理清过程之后再写代码。
代码:
class Solution {
public:
Node *copyRandomList(Node *head) {
if (head == nullptr) return nullptr;
if (head->next == nullptr) {
Node *node = new Node(head->val, nullptr, nullptr);
if (head->random == head) node->random = node;
return node;
}
auto append2Node = [](Node *newnode, Node *oldnode) {
newnode->next = oldnode->next;
oldnode->next = newnode;
};
auto removeFromNode = [](Node *newnode, Node *oldnode) {
oldnode->next = newnode->next;
if (newnode->next != nullptr) newnode->next = newnode->next->next;
};
Node *p;
p = head;
// insert
while (p != nullptr) {
append2Node(new Node(p->val, nullptr, nullptr), p);
p = p->next->next;
}
p = head;
Node *q = p;
// change random
while (p != nullptr) {
q = p->next;
q->random = (p->random != nullptr ? p->random->next : nullptr);
p = q->next;
}
p = head;
Node *res = p->next;
// delete
while (p != nullptr) {
Node *q = p->next->next;
removeFromNode(p->next, p);
p = q;
}
return res;
}
};