持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第18天,点击查看活动详情
139. 单词拆分
(动态规划) O(n^3)
思路
状态表示: f[i]表示字符串s的前i个字符是否可以拆分成wordDict,其值有两个true 和false。
状态计算: 依据最后一次拆分成的字符串str划分集合,最后一次拆分成的字符串str可以为s[0 ~ i - 1],s[1 ~ i - 1],,,s[j ~ i - 1]。
状态转移方程: f[i] = ture 的条件是 :f[j] = ture并且s[j, i - 1]在hash表中存在。
初始化: f[0] = true,表示空串且合法。
实现细节:
为了快速判断字符串s拆分出来的子串在wordDict中出现,我们可以用一个哈希表存贮wordDict中的每个word。
时间复杂度分析: 状态枚举O(n^2),状态计算O(n),因此时间复杂度为O(n^3)。
c++代码
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> hash; //存贮单词
vector<bool> f(s.size() + 1, false);
f[0] = true; //初始化
for(string word : wordDict){
hash.insert(word);
}
for(int i = 1; i <= s.size(); i++){
for(int j = 0; j < i; j++){
if(f[j] && hash.find(s.substr(j, i - j)) != hash.end()){
f[i] = true;
break; //只要有一个子集满足就ok了
}
}
}
return f[s.size()];
}
};
141. 环形链表
思路
(链表,指针扫描) O(n)
用两个指针从头开始扫描,第一个指针每次走一步,第二个指针每次走两步。如果走到 null,说明不存在环;否则如果两个指针相遇,则说明存在环。
为什么呢?
假设链表存在环,则当第一个指针走到环入口时,第二个指针已经走到环上的某个位置,距离环入口还差 x 步。由于第二个指针每次比第一个指针多走一步,所以第一个指针再走 x步,两个指针就相遇了。
如下图所示:
第二个指针还差2步就可以到达环入口,但是第二个指针每次比第一个指针多走1步,因此第一个指针再走2步,两个指针就会相遇。
时间复杂度分析:
第一个指针在环上走不到一圈,所以第一个指针走的总步数小于链表总长度。而第二个指针走的路程是第一个指针的两倍,所以总时间复杂度是O(n)。
c++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
// 只有零个或者1个节点,必然不会成环
if(!head || !head->next) return false;
ListNode *first = head;
ListNode *second = head;
while(second){
first = first->next, second = second->next;
if(!second) return false;
second = second->next;
if(second == first) return true;
}
return false;
}
};
142. 环形链表 II
思路
(链表,快慢指针) O(n)
本题的做法比较巧妙。 用两个指针 first,second分别从起点开始走,first每次走一步,second 每次走两步。 如果过程中 second 走到null,则说明不存在环。否则当 first和 second 相遇后,让 first 返回起点,second 待在原地不动,然后两个指针每次分别走一步,当相遇时,相遇点就是环的入口。
证明: 如上图所示,a 是起点,b 是环的入口,c 是两个指针的第一次相遇点,ab之间的距离是x,bc之间的距离是 y。 则当 first 走到 b 时,由于 second比 first 多走一倍的路,所以 second 已经从 b 开始在环上走了 x 步,可能多余1圈,距离 b 还差 y 步(这是因为第一次相遇点在 b 之后 y 步,我们让 first 退回 b 点,则 second 会退 2y 步,也就是距离 b 点还差 y 步);所以 second从 b 点走 x+y 步即可回到 b 点,所以 second 从 c 点开始走,走 x 步即可恰好走到b 点,同时让 first 从头开始走,走 x 步也恰好可以走到 b 点。所以第二次相遇点就是 b 点。
时间复杂度分析: first 总共走了 2x+y 步,second 总共走了 2x+2y+x 步,所以两个指针总共走了 5x+3y 步。由于当第一次first 走到 b 点时,second 最多追一圈即可追上first,所以 y 小于环的长度,所以 x+y 小于等于链表总长度。所以总时间复杂度是O(n)。
c++代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(!head || !head->next) return NULL;
ListNode *first = head;
ListNode *second = head;
while(second){
first = first->next, second = second->next;
if(!second) return NULL;
second = second->next;
if(second == first){
first = head;
while(first != second){
first = first->next;
second = second->next;
}
return first;
}
}
return NULL;
}
};