剑指力扣
链表
876 链表的中间结点(简单)
法一:两次遍历
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.4 MB, 在所有 C++ 提交中击败了92.96%的用户
class Solution {
public:
ListNode* middleNode(ListNode* head) {
int count = 1;
ListNode* phead = head;
while ((phead = phead->next) != nullptr) count++;
phead = head;
for (int i = 0; i < count / 2; i++) phead = phead->next;
return phead;
}
};
法二:快慢指针
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.4 MB, 在所有 C++ 提交中击败了86.92%的用户
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* fast = head, * slow = head;
while (fast != nullptr &&
fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
剑指 Offer 06 从尾到头打印链表(简单)
执行用时:4 ms, 在所有 C++ 提交中击败了94.51%的用户
内存消耗:8.6 MB, 在所有 C++ 提交中击败了85.66%的用户
法一:辅助栈
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
if (head == nullptr) return {};
ListNode* phead = head;
stack<int> s;
vector<int> res;
while (phead != nullptr) {
s.push(phead->val);
phead = phead->next;
}
while (!s.empty()) {
res.push_back(s.top());
s.pop();
}
return res;
}
};
法二:递归
class Solution {
public:
void GetAns(vector<int>& res, ListNode* head) {
if (head->next != nullptr) GetAns(res, head->next);
res.push_back(head->val);
}
vector<int> reversePrint(ListNode* head) {
if (head == nullptr) return {};
vector<int> res;
GetAns(res, head);
return res;
}
};
法三:先反转链表,再输入
执行用时:4 ms, 在所有 C++ 提交中击败了94.51%的用户
内存消耗:8.5 MB, 在所有 C++ 提交中击败了92.68%的用户
参考类似题目:206 反转链表 & 剑指 Offer 24 反转链表
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> res;
if (!head) return res;
//先反转链表
auto a = head, b = head->next;
while (b) {
auto c = b->next;
b->next = a;
a = b, b = c;
}
head->next = nullptr;
//反转完毕,a是头结点
while (a) {
res.push_back(a->val);
a = a->next;
}
return res;
}
};
法四:反转vector容器(思路最简单)
执行用时:8 ms, 在所有 C++ 提交中击败了66.80%的用户
内存消耗:8.4 MB, 在所有 C++ 提交中击败了94.44%的用户
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> res;
if (head == nullptr) return {};
while (head != nullptr) {
res.push_back(head->val);
head = head->next;
}
reverse(res.begin(), res.end());
return res;
}
};
141 环形链表(简单)
法一:快慢指针法:
执行用时:8 ms, 在所有 C++ 提交中击败了93.73%的用户
内存消耗:7.9 MB, 在所有 C++ 提交中击败了29.02%的用户
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) return false;
ListNode* fast= head, * slow = head;
while (fast != nullptr) {
fast = fast->next;
if (fast == nullptr) break;
fast = fast->next;
slow = slow->next;
if (fast == slow) return true;
}
return false;
}
};
法二:哈希表法:(效率低,但是是一种思路)
执行用时:20 ms, 在所有 C++ 提交中击败了16.61%的用户
内存消耗:9.8 MB, 在所有 C++ 提交中击败了5.13%的用户
class Solution {
public:
bool hasCycle(ListNode* head) {
unordered_set<ListNode*> hash;
while (head != nullptr) {
if (hash.count(head)) return true;
hash.insert(head);
head = head->next;
}
return false;
}
};
24. 两两交换链表中的节点(简单)
执行用时:4 ms, 在所有 C++ 提交中击败了75.25%的用户
内存消耗:7.2 MB, 在所有 C++ 提交中击败了97.63%的用户
找准“中间态”即可。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* phead = new ListNode, * first = phead, * second = head, * third = head->next;
phead->next = head;
while (third != nullptr) {
first->next = third;
second->next = third->next;
third->next = second;
if (second->next == nullptr || second->next->next == nullptr) break;
first = second;
second = second->next;
third = second->next;
}
return phead->next;
}
};
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode* dummy = new ListNode, * p_left = dummy, * p_middle = head, * p_right = head->next;
while (p_right != nullptr) {
p_middle->next = p_right->next;
p_right->next = p_middle;
p_left->next = p_right;
p_right = p_middle->next;
if (p_right == nullptr) break;
p_right = p_right->next;
p_left = p_middle;
p_middle = p_middle->next;
}
return dummy->next;
}
};
2. 两数相加
执行用时:56 ms, 在所有 C++ 提交中击败了43.45%的用户
内存消耗:69.4 MB, 在所有 C++ 提交中击败了88.33%的用户
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* p1 = l1, * p2 = l2;
int len1 = 0, len2 = 0;
while (p1 != nullptr) { p1 = p1->next; len1++; }
while (p2 != nullptr) { p2 = p2->next; len2++; }
if (len1 < len2) return addTwoNumbers(l2, l1);
p1 = l1, p2 = l2;
while (p2 != nullptr) {
p1->val += p2->val;
p1 = p1->next, p2 = p2->next;
}
p1 = l1;
while (p1->next != nullptr) {
if (p1->val > 9) {p1->val -= 10; p1->next->val++; }
p1 = p1->next;
}
if (p1->val > 9) {
ListNode* new_node = new ListNode;
new_node->next = nullptr;
new_node->val = 1;
p1->next = new_node;
p1->val -= 10;
}
return l1;
}
};
19. 删除链表的倒数第 N 个结点
执行用时:8 ms, 在所有 C++ 提交中击败了54.91%的用户
内存消耗:10.4 MB, 在所有 C++ 提交中击败了86.38%的用户
关键:如何删除倒数最后一个结点(也就是头结点)?——创建虚拟头结点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(), * fast = dummy, * slow = fast;
fast->next = head;
while (n-- >= 0) fast = fast->next;
while (fast) fast = fast->next, slow = slow->next;
slow->next = slow->next->next;
return dummy->next;
}
};
237. 删除链表中的节点(简单)
执行用时:8 ms, 在所有 C++ 提交中击败了97.87%的用户
内存消耗:7.6 MB, 在所有 C++ 提交中击败了76.12%的用户
脑筋急转弯!
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
61. 旋转链表(中等)
数组
剑指 Offer 03. 数组中重复的数字(简单)
法一:哈希表
执行用时:132 ms, 在所有 C++ 提交中击败了30.13%的用户
内存消耗:27.3 MB, 在所有 C++ 提交中击败了20.05%的用户
class Solution {
public:
int findRepeatNumber(vector<int>& nums) {
unordered_set<int> table;
for (int i = 0; i < nums.size(); i++) {
if (table.count(nums[i]) > 0) return nums[i];
table.insert(nums[i]);
//思考:为什么下面不行?
// table.insert(nums[i]);
// if (table.count(nums[i]) > 1) return nums[i];
}
return 0;
}
};
法二:原地置换
无~
剑指 Offer 45. 把数组排成最小的数(中等)
执行用时:12 ms, 在所有 C++ 提交中击败了72.75%的用户
内存消耗:11.7 MB, 在所有 C++ 提交中击败了14.89%的用户
class Solution {
public:
static bool str_cmp(const string& str1, const string& str2) {
return str1 + str2 < str2 + str1 ? true : false;
}
string minNumber(vector<int>& nums) {
int n = nums.size();
vector<string> str;
string res = "";
if (n == 0) return res;
for (int i = 0; i < n; i++) str.push_back(to_string(nums[i]));
sort(str.begin(), str.end(), str_cmp);
for (int j = 0; j < str.size(); j++) res += str[j];
return res;
}
};
难点:
1.定义排序规则:若字符串 x + y > y + x。则定义 x > y。
2.定义排序函数
剑指 Offer 56 - II. 数组中数字出现的次数 II(中等)
剑指 Offer 56 - II. 数组中数字出现的次数 II
法一:哈希表
执行用时:76 ms, 在所有 C++ 提交中击败了74.05%的用户
内存消耗:18.4 MB, 在所有 C++ 提交中击败了24.49%的用户
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int, int> table;
for (int i = 0; i < nums.size(); i++) table[nums[i]]++;
for (unordered_map<int, int>::iterator it = table.begin(); it != table.end(); it++)
if (it->second == 1)
return it->first;
/*用下标访问也可:
for (int i = 0; i < nums.size(); i++)
if (table[nums[i]] == 1)
return nums[i];
*/
return -1;
}
};
法二:二进制各位数字出现的次数
执行用时:80 ms, 在所有 C++ 提交中击败了68.87%的用户
内存消耗:16.4 MB, 在所有 C++ 提交中击败了33.36%的用户
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0, arr[32] = { 0 }; //记录各个位上1的个数
for (int i = 0; i < nums.size(); i++) {
for (int j = 0; j < 32; j++) {
if (nums[i] % 2 != 0) arr[j]++;
nums[i] /= 2;
}
}
for (int k = 0; k < 32; k++)
if (arr[k] % 3 != 0)
res += pow(2, k);
return res;
}
};
46. 全排列(中等)
执行用时:4 ms, 在所有 C++ 提交中击败了92.27%的用户
内存消耗:7.8 MB, 在所有 C++ 提交中击败了86.53%的用户
class Solution {
public:
int Num(int num) {
int res = 1;
for (int i = 1; i <= num; i++) res *= i;
return res;
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int> > res;
for (int i = 0; i < Num(nums.size()); i++) {
res.push_back(nums);
next_permutation(nums.begin(), nums.end());
}
return res;
}
};
使用algorithm中的next_permutation函数会使这道题变得很无脑。以后有机会尝试用其他方法解决。
不要过度依赖algorithm库!
239. 滑动窗口最大值(困难)
滑动窗口法:(超时)
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> res;
for (int i = 0; i < nums.size() - k + 1; i++) {
int max = INT_MIN;
for (int j = 0; j < k; i++) {
if (nums[j] > max) max = nums[j];
}
res.push_back(max);
}
return res;
}
};
字符串
387. 字符串中的第一个唯一字符(简单)
执行用时:28 ms, 在所有 C++ 提交中击败了80.91%的用户
内存消耗:10.6 MB, 在所有 C++ 提交中击败了93.55%的用户
class Solution {
public:
int firstUniqChar(string s) {
int table[26] = { 0 };
for (int i = 0; i < s.size(); i++)
table[s[i] - 'a']++;
for (int j = 0; j < s.size(); j++)
if (table[s[j] - 'a'] == 1)
return j;
return -1;
}
};
567. 字符串的排列(中等)
执行用时:368 ms, 在所有 C++ 提交中击败了5.02%的用户
内存消耗:7.4 MB, 在所有 C++ 提交中击败了79.27%的用户
滑动窗口+哈希表
class Solution {
public:
bool checkInclusion(string s1, string s2) {
int n1 = s1.size(), n2 = s2.size(), table[26] = { 0 };
for (int i = 0; i < n1; i++) table[s1[i] - 'a']++;
for (int j = 0; j < n2; j++) {
int tmp[26]; //创建table的复制版
for (int k = 0; k < 26; k++) tmp[k] = table[k];
int count = 0, index_s2 = j;
while (index_s2 < n2 && tmp[s2[index_s2++] - 'a']-- > 0) count++;
if (count == n1) return true;
}
return false;
}
};
14.最长公共前缀(简单)
执行用时:4 ms, 在所有 C++ 提交中击败了92.77%的用户
内存消耗:9.5 MB, 在所有 C++ 提交中击败了33.07%的用户
纵向扫描法
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.size() == 0) return "";
string res = "";
for (int j = 0; ; j++) { //i代表行数,j代表列数
for (int i = 0; i < strs.size(); i++) {
char c = strs[0][j];
if (c != strs[i][j] || j == strs[i].size()) return res;
}
res += strs[0][j];
}
return res;
}
};
可以尝试横向扫描法。
38. 外观数列(简单)
执行用时:8 ms, 在所有 C++ 提交中击败了70.79%的用户
内存消耗:6.5 MB, 在所有 C++ 提交中击败了88.91%的用户
这道题作为专门的套路,值得背下来!
class Solution {
public:
string countAndSay(int n) {
string s = "1";
for (int i = 0; i < n - 1; i++) {
string tmp;
for (int j = 0; j < s.size(); ) {
int k = j;
while (j < s.size() && s[k] == s[j]) j++;
tmp += to_string(j - k) + s[k];
}
s = tmp;
}
return s;
}
};
49. 字母异位词分组(中等)
法一:哈希表法
执行用时:28 ms, 在所有 C++ 提交中击败了99.77%的用户
内存消耗:19.1 MB, 在所有 C++ 提交中击败了77.88%的用户
记住套路,更关键的是搞清哈希表的对应关系!
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string> > res;
unordered_map<string, vector<string> > hash;
for (auto& value : strs) {
string key = value;
sort(key.begin(), key.end());
hash[key].push_back(value);
}
for (auto& item : hash) res.push_back(item.second);
return res;
}
};
151. 翻转字符串里的单词(中等)
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.9 MB, 在所有 C++ 提交中击败了88.18%的用户
难点一:先反转每一个单词,再反转整个字符串。
难点二:去除单词之间多余的空格
class Solution {
public:
string reverseWords(string s) {
int left = 0, right = 0, index = 0;
while (right < s.size()) {
while (right < s.size() && s[right] == ' ') right++;
if (right == s.size()) break;
left = right;
while (right < s.size() && s[right] != ' ') right++;
reverse(s.begin() + left, s.begin() + right);
if (index) s[index++] = ' ';
//难点二:
while (left < right) s[index++] = s[left++];
}
s.erase(s.begin() + index, s.end());
reverse(s.begin(), s.end());
return s;
}
};
165. 比较版本号
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6 MB, 在所有 C++ 提交中击败了97.27%的用户
这道题的很多函数都很重要!需要背下来!
class Solution {
public:
int compareVersion(string s1, string s2) {
int p1 = 0, p2 = 0;
while (p1 < s1.size() || p2 < s2.size()) {
int x = p1, y = p2;
while (p1 < s1.size() && s1[p1] != '.') p1++;
while (p2 < s2.size() && s2[p2] != '.') p2++;
int a = x == p1 ? 0 : atoi(s1.substr(x, p1 - x).c_str());
int b = y == p2 ? 0 : atoi(s2.substr(y, p2 - y).c_str());
if (a > b) return 1;
else if (a < b) return -1;
p1 += 1, p2 += 1;
}
return 0;
}
};
929. 独特的电子邮件地址(简单)
思路一:
执行用时:24 ms, 在所有 C++ 提交中击败了96.21%的用户
内存消耗:12.9 MB, 在所有 C++ 提交中击败了88.49%的用户
class Solution {
public:
int numUniqueEmails(vector<string>& emails) {
unordered_set<string> hash;
for (auto& letter : emails) {
int index = 0;
string tmp;
for (; letter[index] != '@'; index++) {
if (letter[index] == '.') continue;
if (letter[index] == '+') {
while (letter[index] != '@') index++;
break;
}
tmp += letter[index];
}
for (; index < letter.size(); index++)
tmp += letter[index];
hash.insert(tmp);
}
return hash.size();
}
};
思路二:
执行用时:40 ms, 在所有 C++ 提交中击败了51.49%的用户
内存消耗:13.5 MB, 在所有 C++ 提交中击败了66.03%的用户
熟悉substr()函数的用法
class Solution {
public:
int numUniqueEmails(vector<string>& emails) {
unordered_set<string> hash;
for (auto& email : emails) {
string name;
int at = email.find('@');
for (auto c : email.substr(0, at))
if (c == '+') break;
else if (c != '.') name += c;
name += email.substr(at);
hash.insert(name);
}
return hash.size();
}
};
5. 最长回文子串
方法一:中心点扩散法
执行用时:48 ms, 在所有 C++ 提交中击败了73.05%的用户
内存消耗:23.9 MB, 在所有 C++ 提交中击败了56.25%的用户
class Solution {
public:
string longestPalindrome(string s) {
string res;
for (int i = 0; i < s.size(); i++) {
for (int left = i, right = i; left >= 0 && right < s.size() && s[left] == s[right]; left--, right++)
if (res.size() < right - left + 1) res = s.substr(left, right - left + 1);
for (int left = i, right = i + 1; left >= 0 && right < s.size() && s[left] == s[right]; left--, right++)
if (res.size() < right - left + 1) res = s.substr(left, right - left + 1);
}
return res;
}
};
6. Z 字形变换(中等)
执行用时:4 ms, 在所有 C++ 提交中击败了98.99%的用户
内存消耗:8.2 MB, 在所有 C++ 提交中击败了89.53%的用户
class Solution {
public:
string convert(string s, int n) {
if (n == 1) return s;
string res;
for (int i = 0; i < n; i++) {
if (i == 0 || i == n - 1) {
for (int j = i; j < s.size();
j += 2 * (n - 1))
res += s[j];
continue;
}
for (int k1 = i, k2 = 2 * (n - 1) - i;
k1 < s.size() || k2 < s.size();
k1 += 2 * (n - 1), k2 += 2 * (n - 1)) {
if (k1 < s.size()) res += s[k1];
if (k2 < s.size()) res += s[k2];
}
}
return res;
}
};
3. 无重复字符的最长子串(中等)
法一:哈希表法
思路一:
执行用时:32 ms, 在所有 C++ 提交中击败了63.44%的用户
内存消耗:10.7 MB, 在所有 C++ 提交中击败了36.29%的用户
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
unordered_set<char> hash;
for (int left = 0, right = 0; right < s.size(); right++) {
while (hash.count(s[right])) {
hash.erase(s[left]);
left++;
}
hash.insert(s[right]);
res = max(res, right - left + 1);
}
return res;
}
};
思路二:
执行用时:16 ms, 在所有 C++ 提交中击败了75.92%的用户
内存消耗:8.1 MB, 在所有 C++ 提交中击败了67.48%的用户
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
unordered_map<char, int> hash;
for (int left = 0, right = 0; right < s.size(); right++) {
hash[s[right]]++;
while (hash[s[right]] > 1) hash[s[left++]]--;
res = max(res, right - left + 1);
}
return res;
}
};
法二:暴力两遍扫描
执行用时:1076 ms, 在所有 C++ 提交中击败了5.00%的用户
内存消耗:238.3 MB, 在所有 C++ 提交中击败了4.97%的用户
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int length = 0;
for (int i = 0; i < s.size(); i++) {
unordered_set<char> hash;
for (int j = i; j < s.size(); j++) {
if (hash.count(s[j])) {
if (length < j - i) length = j - i;
break;
}
if (j == s.size() - 1)
if (length < j - i + 1) length = j - i + 1;
hash.insert(s[j]);
}
}
return length;
}
};
208. 实现 Trie (前缀树)(中等)
执行用时:72 ms, 在所有 C++ 提交中击败了88.26%的用户
内存消耗:43.9 MB, 在所有 C++ 提交中击败了29.46%的用户
class Trie {
public:
struct Node {
Node* sons[26];
bool is_end;
Node() {
is_end = false;
for (int i = 0; i < 26; i++) sons[i] = nullptr;
}
}*root;
/** Initialize your data structure here. */
Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
void insert(string word) {
Node* new_node = root;
for (auto c : word) {
if (!new_node->sons[c - 'a']) new_node->sons[c - 'a'] = new Node();
new_node = new_node->sons[c - 'a'];
}
new_node->is_end = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
Node* find = root;
for (auto c : word) {
if (!find->sons[c - 'a']) return false;
find = find->sons[c - 'a'];
}
return find->is_end;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
Node* find = root;
for (auto c : prefix) {
if (!find->sons[c - 'a']) return false;
find = find->sons[c - 'a'];
}
return true;
}
};
273. 整数转换英文表示
贪心
455. 分发饼干(简单)
执行用时:32 ms, 在所有 C++ 提交中击败了99.05%的用户
内存消耗:17.1 MB, 在所有 C++ 提交中击败了98.40%的用户
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int index_s = 0, index_g = 0;
while (index_s < s.size() && index_g < g.size())
if (g[index_g] <= s[index_s]) { index_s++; index_g++; }
else index_s++;
return index_g;
}
};
贪心的标志算法
55.跳跃游戏(中等)
执行用时:8 ms, 在所有 C++ 提交中击败了99.24%的用户
内存消耗:12.5 MB, 在所有 C++ 提交中击败了94.64%的用户
class Solution {
public:
bool canJump(vector<int>& nums) {
int dist = 0;
for (int i = 0; i <= dist && i < nums.size(); i++)
dist = max(dist, nums[i] + i);
return dist >= nums.size() - 1;
}
};
45. 跳跃游戏II
执行用时:8 ms, 在所有 C++ 提交中击败了99.81%的用户
内存消耗:15.1 MB, 在所有 C++ 提交中击败了93.23%的用户
class Solution {
public:
int jump(vector<int>& nums) {
if (nums.size() == 1) return 0;
int left = 0, right = 0, steps = 0; //用left和right维护一个区间
while (left <= right) {
int max_r = 0;
for (int i = left; i <= right; i++)
max_r = max(max_r, nums[i] + i);
left = right, right = max_r;
steps++;
if (right >= (int)nums.size() - 1) break;
}
return steps;
}
};
位运算
231. 2的幂(简单)
方法一:
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.7 MB, 在所有 C++ 提交中击败了98.60%的用户
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (1 << 30) % n == 0;
}
};
方法二:
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.8 MB, 在所有 C++ 提交中击败了97.10%的用户
class Solution {
public:
bool isPowerOfTwo(int n) {
return n > 0 && (n & -n) == n;
}
};
x & -x表示x的二进制中最靠近右边的1及以后的数
762. 二进制表示中质数个计算置位(简单)
执行用时:40 ms, 在所有 C++ 提交中击败了39.26%的用户
内存消耗:6.2 MB, 在所有 C++ 提交中击败了58.97%的用户
class Solution {
public:
int countPrimeSetBits(int L, int R) {
unordered_set<int> table = { 2,3,5,7,11,13,17,19,23,29,31 };
int num = 0;
for (int i = L; i <= R; i++) {
int n = 0;
for (int j = i; j; j >>= 1) n += (j & 1); //计算二进制1的个数。常见技巧!
if (table.count(n)) num++;
}
return num;
}
};
136. 只出现一次的数字
执行用时:12 ms, 在所有 C++ 提交中击败了99.83%的用户
内存消耗:16.5 MB, 在所有 C++ 提交中击败了96.73%的用户
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto x : nums) ret ^= x;
return ret;
}
};
技巧: a ^ a = 0 a ^ 0 = a
137. 只出现一次的数字 II(中等)
执行用时:8 ms, 在所有 C++ 提交中击败了95.41%的用户
内存消耗:9.1 MB, 在所有 C++ 提交中击败了99.32%的用户
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (int i = 0; i < 32; i++) {
int j, value = 0;
for (j = 0; j < nums.size(); j++)
value += (nums[j] >> i) & 1;
if (value % 3) ret += (1 << i);
}
return ret;
}
};
260 只出现一次的数字 III(中等)
执行用时:12 ms, 在所有 C++ 提交中击败了92.41%的用户
内存消耗:9.8 MB, 在所有 C++ 提交中击败了93.97%的用户
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int p1 = 0, p2 = 0, i;
for (auto x : nums) p1 ^= x;
for (i = 0; i < 32; i++)
if ((p1 >> i) & 1) break;
for (auto y : nums) if ((y >> i) & 1) p2 ^= y;
return {p2, p1 ^ p2};
}
};
哈希
1. 两数之和(简单)
执行用时:8 ms, 在所有 C++ 提交中击败了90.24%的用户
内存消耗:9.9 MB, 在所有 C++ 提交中击败了25.03%的用户
心得:使用哈希表时,找准对应关系:nums[i]对应hash的key值,i(下标)对应hash的value值。count函数用于查找key是否出现。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) hash[nums[i]] = i;
for (int j = 0; j < nums.size(); j++) {
int value = target - nums[j];
if (hash.count(value) && hash[value] != j) return {j, hash[value]};
}
return {};
}
};
考虑到在填充哈希表的过程中,可以随时检测是否找到目标值,因此出现了以下的优化算法。
执行用时:4 ms, 在所有 C++ 提交中击败了98.88%的用户
内存消耗:8.7 MB, 在所有 C++ 提交中击败了94.20%的用户
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//key用于记录nums中的值,value记录nums中对应的值的下标
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) {
//先判断“补数”是否存在,防止“补数”是自己的情况发生
int value = target - nums[i];
//count函数用于记录key出现的次数
if (hash.count(value))
return {i, hash[value]};
hash[nums[i]] = i;
}
return {};
}
};
注:也可以用两层for暴力求解。
454. 四数相加 II(中等)
执行用时:316 ms, 在所有 C++ 提交中击败了96.66%的用户
内存消耗:45.4 MB, 在所有 C++ 提交中击败了37.66%的用户
这道题是第1题的升级版,思路几乎一样,唯一的技巧就是将两两数组看成一个整体。
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
int res = 0;
unordered_map<int, int> hash;
for (auto& x : A)
for (auto& y : B)
hash[x + y]++;
for (auto& x : C)
for (auto& y : D)
res += hash[-(x + y)];
return res;
}
};
560. 和为K的子数组(中等)
思路:任何一个连续子数组都可以由两个前缀和数组的差表示。
例如:3,4,5号位数之和可以由前5号位之和减去前(3-1)号位之和表示。
执行用时:132 ms, 在所有 C++ 提交中击败了81.37%的用户
内存消耗:40.5 MB, 在所有 C++ 提交中击败了27.67%的用户
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int res = 0, pre = 0; //pre表示前n项和
//hash的key值表示前n项和,value表示该key值出现的次数。
unordered_map<int, int> hash;
hash[0] = 1; //为什么要有这个临界条件?
for (int i = 0; i < nums.size(); i++) {
pre += nums[i];
res += hash[pre - k];
hash[pre]++;
}
return res;
}
};
二分
69. x 的平方根(简单)
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.9 MB, 在所有 C++ 提交中击败了91.55%的用户
- 一般情况下,mid值会先停留在目标值处。
- keypoint1决定了mid值(目标值)的平方一定小于或等于x,满足题意。
- 紧接着左边界会停留在mid值处,也就是目标值处。
- 让左边界还是右边界先停留在目标值,是要根据第二行决定的。
- 最终左右边界会挨在一起。例如:左边界为4,右边界为5。
- 为了让left < right。需要让right = mid - 1。
- 当挨在一起的时候,mid值不能是目标值。因此需要微调keypoint3语句。
class Solution {
public:
int mySqrt(int x) {
int left = 0, right = x;
while (left < right) {
int mid = (1ll + left + right) / 2; //keypoint3
if (1ll * mid * mid <= x) left = mid; //keypoint1
else right = mid - 1; //keypoint2
}
return left;
}
};
步骤:
- 首先写框架:
int mySqrt(int x) {
int left = 0, right = x;
while (left < right) {
int mid = (left + right) / 2;
if (mid * mid <= x)
}
}
- 然后决定让左边界靠近mid值:
//int mySqrt(int x) {
// int left = 0, right = x;
// while (left < right) {
// int mid = (left + right) / 2;
// if (mid * mid <= x)
left = mid;
// }
//}
- 死记硬背下面的else语句:
//int mySqrt(int x) {
// int left = 0, right = x;
// while (left < right) {
// int mid = (left + right) / 2;
// if (mid * mid <= x) left = mid;
else right = mid - 1;
// }
//}
- 当左右边界靠在一起的时候,mid值不能是目标值(很玄幻,也很关键):
//int mySqrt(int x) {
// int left = 0, right = x;
// while (left < right) {
int mid = (left + right + 1) / 2;
// if (mid * mid <= x) left = mid;
// else right = mid - 1;
// }
//}
- 修改细节,返回答案
int mySqrt(int x) {
int left = 0, right = x;
while (left < right) {
int mid = (1ll + left + right) / 2;
if (1ll * mid * mid <= x) left = mid;
else right = mid - 1;
}
return left;
}
34. 在排序数组中查找元素的第一个和最后一个位置(中等)
执行用时:8 ms, 在所有 C++ 提交中击败了98.43%的用户
内存消耗:13.3 MB, 在所有 C++ 提交中击败了94.28%的用户
法一:二分法(很重要!)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if (nums.empty()) return {-1, -1};
int left = 0, right = nums.size() - 1;
vector<int> res;
while (left < right) {
int mid = left + right >> 1; //重要
if (nums[mid] >= target) right = mid;
else left = mid + 1;
}
if (nums[right] != target) return {-1, -1};
res.push_back(right);
left = right, right = nums.size() - 1;
while (left < right) {
int mid = left + right + 1 >> 1; //重要
if (nums[mid] <= target) left = mid;
else right = mid - 1;
}
res.push_back(left);
return res;
}
};
法二:一遍扫描
执行用时:4 ms, 在所有 C++ 提交中击败了99.86%的用户
内存消耗:13.3 MB, 在所有 C++ 提交中击败了94.95%的用户
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int start = -1, end = -1;
for (int i = 0; i < nums.size(); i++) {
if (start == -1 && nums[i] == target)
start = i, end = i;
if (start != -1 && nums[i] == target)
end = i;
}
return {start, end};
}
};
162. 寻找峰值(中等)
方法一:二分法:
执行用时:4 ms, 在所有 C++ 提交中击败了96.27%的用户
内存消耗:8.7 MB, 在所有 C++ 提交中击败了81.79%的用户
难点是识别出二分法,而二分法很简单。
方法二:一遍扫描:
执行用时:4 ms, 在所有 C++ 提交中击败了95.43%的用户
内存消耗:8.6 MB, 在所有 C++ 提交中击败了83.42%的用户
int findPeakElement(vector<int>& nums) {
int res = nums[0] > nums[nums.size() - 1] ? 0 : nums.size() - 1;
for (int i = 1; i < nums.size() - 1; i++) {
if (nums[i] > nums[i - 1] && nums[i] > nums[i + 1]) {
res = i;
break;
}
}
return res;
}
74. 搜索二维矩阵(中等)
执行用时:8 ms, 在所有 C++ 提交中击败了66.30%的用户
内存消耗:9.3 MB, 在所有 C++ 提交中击败了81.93%的用户
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
if (m == 0 || n == 0) return false;
int left = 0, right = m * n - 1;
while (left < right) {
int mid = left + right >> 1;
if (matrix[mid / n][mid % n] >= target)
right = mid; //有点绕
else left = mid + 1;
}
return matrix[right / n][right % n] == target;
}
};
240. 搜索二维矩阵 II(中等)
执行用时:116 ms, 在所有 C++ 提交中击败了91.97%的用户
内存消耗:14.5 MB, 在所有 C++ 提交中击败了58.02%的用户
这道题和二分法没啥关系,是一个固定的套路题
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if (matrix.empty() || matrix[0].empty()) return false;
int i = 0, j = matrix[0].size() - 1;
while (i < matrix.size() && j >= 0) {
if (target == matrix[i][j]) return true;
else if (target < matrix[i][j]) j--;
else i++;
}
return false;
}
};
栈
155. 最小栈
执行用时:20 ms, 在所有 C++ 提交中击败了98.78%的用户
内存消耗:15.5 MB, 在所有 C++ 提交中击败了15.16%的用户
单调栈:套路
难点是在常数时间内找出最小值。
class MinStack {
stack<int> stk;
stack<int> min;
public:
MinStack() {
}
void push(int x) {
stk.push(x);
if (min.empty() || x <= min.top()) min.push(x);
}
void pop() {
if (stk.top() == min.top()) min.pop();
stk.pop();
}
int top() {
return stk.top();
}
int getMin() {
return min.top();
}
};
496. 下一个更大元素 I(简单)
法一:单调栈
执行用时:4 ms, 在所有 C++ 提交中击败了98.73%的用户
内存消耗:8.7 MB, 在所有 C++ 提交中击败了70.46%的用户
都是套路,全靠记住
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int> hash;
stack<int> stk;
vector<int> res;
for (int i = nums2.size() - 1; i >= 0; i--) {
//先弹栈
while (stk.size() && nums2[i] > stk.top())
stk.pop();
hash[nums2[i]] = stk.size() ? stk.top() : -1;
//再压栈
stk.push(nums2[i]);
}
for (auto x : nums1) res.push_back(hash[x]);
return res;
}
};
法二:暴力求解
执行用时:20 ms, 在所有 C++ 提交中击败了25.61%的用户
内存消耗:8.1 MB, 在所有 C++ 提交中击败了98.77%的用户
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
for (int i = 0; i < nums1.size(); i++) {
int j = 0;
while (nums2[j] != nums1[i]) j++;
int tmp = nums2[j];
while (j < nums2.size())
if (nums2[j] > tmp) {
res.push_back(nums2[j]);
break;
}
else j++;
if (j == nums2.size()) res.push_back(-1);
}
return res;
}
};
DFS和BFS
199. 二叉树的右视图
执行用时:4 ms, 在所有 C++ 提交中击败了79.76%的用户
内存消耗:11.5 MB, 在所有 C++ 提交中击败了83.17%的用户
显然要一层一层地搜索,考虑使用宽搜(BFS)。并且借助队列。
推荐视频:宽度搜索
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
if (!root) return res;
queue<pair<TreeNode*, int>> q;
q.push(make_pair(root, 0));
while (q.size()) {
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if (res.size() <= level) res.push_back(0);
res[level] = node->val;
if (node->left) q.push(make_pair(node->left, level + 1));
if (node->right) q.push(make_pair(node->right, level + 1));
}
return res;
}
};
236. 二叉树的最近公共祖先
执行用时:12 ms, 在所有 C++ 提交中击败了98.91%的用户
内存消耗:16.8 MB, 在所有 C++ 提交中击败了16.58%的用户
心得:
-
通过深搜,搜出两个节点的路径。例如:路径一(3,1,2,4),路径二(3,1,5)。则最近公共祖先为1。
-
使用深搜,则一定需要递归。递归的结束条件有两个:一是遇到目标,二是遇到空值。多看几次代码,熟悉这个模型。
-
脑袋中要有深搜的一个动态图。
推荐视频:深度搜索
class Solution {
public:
void dfs_search(vector<TreeNode*>& stk, vector<TreeNode*>& path, TreeNode* target, TreeNode* node) {
if (!node) return;
stk.push_back(node);
if (target == node) {
path = stk;
return;
}
dfs_search(stk, path, target, node->left); //一定会先把最左边的路走完
dfs_search(stk, path, target, node->right); //然后依次从远离根结点的右边的结点向根结点靠近
stk.pop_back(); //当一个结点的左右子结点都扫描完后,应该弹出该结点。
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> stk, p_path, q_path;
dfs_search(stk, p_path, p, root);
stk.clear();
dfs_search(stk, q_path, q, root);
int common = 0;
while (common < p_path.size() && common < q_path.size() && p_path[common] == q_path[common]) common++;
return p_path[--common];
}
};
279. 完全平方数(中等)
执行用时:96 ms, 在所有 C++ 提交中击败了85.14%的用户
内存消耗:11.8 MB, 在所有 C++ 提交中击败了19.19%的用户
- 创建一个列表,列表的下标表示正整数,值表示最少需要多少步到达原点0。
- 创建队列,队列的值表示dist中的下标值。
- 初始化列表dist,dist[0] = 0,初始化队列,放入下标0。
class Solution {
public:
int numSquares(int n) {
queue<int> q;
vector<int> dist(n + 1, INT_MAX);
q.push(0), dist[0] = 0;
while (q.size()) {
int num = q.front();
q.pop();
if (num == n) return dist[num];
for (int i = 1; i * i + num <= n; i++) {
int j = i * i + num;
if (dist[j] > dist[num] + 1) {
dist[j] = dist[num] + 1;
q.push(j);
}
}
}
return 0;
}
};
733. 图像渲染(简单)
执行用时:12 ms, 在所有 C++ 提交中击败了84.09%的用户
内存消耗:13.4 MB, 在所有 C++ 提交中击败了80.65%的用户
步骤一:做一个向四个方向发散的深搜
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
for (int i = 0; i < 4; i++) {
int x = dx[i] + sr, y = dx[i] + sc;
if (x >= 0 && x < image.size()
&& y >= 0 && y < image[0].size()
&& image[x][y] == image[sr][sc])
floodFill(image, x, y, newColor);
}
步骤二:在每一个floodFill函数中修改color值
//vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
// int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
int oldColor = image[sr][sc];
// image[sr][sc] = newColor;
// for (int i = 0; i < 4; i++) {
// int x = dx[i] + sr, y = dx[i] + sc;
// if (x >= 0 && x < image.size()
// && y >= 0 && y < image[0].size()
&& image[x][y] == oldColor)
// floodFill(image, x, y, newColor);
// }
//}
步骤三:判断一些细节步骤:
class Solution {
public:
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
if (image.empty() || image[0].empty()) return image;
int dy[4] = { -1, 0, 1, 0 }, dx[4] = { 0, 1, 0, -1 };
int oldColor = image[sr][sc];
if (oldColor == newColor) return image;
image[sr][sc] = newColor;
for (int i = 0; i < 4; i++) {
int x = sr + dx[i], y = sc + dy[i];
if (x >= 0 && x < image.size() && y >= 0 && y < image[0].size() && image[x][y] == oldColor)
floodFill(image, x, y, newColor);
}
return image;
}
};
200. 岛屿数量(中等)
执行用时:16 ms, 在所有 C++ 提交中击败了96.26%的用户
内存消耗:9.3 MB, 在所有 C++ 提交中击败了90.60%的用户
这道题和733题很类似。
class Solution {
public:
void dfs_serach(vector<vector<char>>& grid, int sr, int sc, int& res) {
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, 1, 0, -1 };
grid[sr][sc] = '0';
for (int k = 0; k < 4; k++) {
int x = dx[k] + sr, y = dy[k] + sc;
if (x >= 0 && x < grid.size() &&
y >= 0 && y < grid[0].size() &&
grid[x][y] == '1')
dfs_serach(grid, x, y, res);
}
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size(), res = 0;
if (!m || ! n) return 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
res++;
dfs_serach(grid, i, j, res);
}
}
}
return res;
}
};
130. 被围绕的区域(中等)
784. 字母大小写全排列(中等)
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:9.8 MB, 在所有 C++ 提交中击败了78.00%的用户
class Solution {
public:
vector<string> letterCasePermutation(string S) {
vector<string> res;
dfs(S, res, 0);
return res;
}
void dfs(string S, vector<string>& res, int n) {
if (n == S.size()) { //字符串的末尾还有一个'\0'
res.push_back(S);
return;
}
dfs(S, res, n + 1);
if (S[n] >= 'A') {
S[n] ^= ' ';
dfs(S, res, n + 1);
}
}
};
77. 组合(中等)
执行用时:24 ms, 在所有 C++ 提交中击败了69.82%的用户
内存消耗:9.8 MB, 在所有 C++ 提交中击败了64.98%的用户
脑袋里要有一个树状图。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combine(int n, int k) {
dfs(n, k, 1);
return res;
}
void dfs(int n, int k, int start) {
if (!k) {
res.push_back(path);
return;
}
for (int i = start; i <= n; i++) {
path.push_back(i);
dfs(n, k - 1, i + 1);
path.pop_back();
}
}
};
步骤一:写出深搜的框架
vector<vector<int>> res;
vector<vector<int>> combine(int n, int k) {
dfs();
return res;
}
void dfs() {}
步骤二:将每一次搜索的结果存入一个临时变量path中,并在深搜结束后放入结果res中。
// vector<vector<int>> res;
vector<int> path;
// vector<vector<int>> combine(int n, int k) {
// dfs();
// return res;
// }
void dfs() {
if (终止条件) {
res.push_back(path);
return;
}
}
步骤三:考虑dfs的参数
// vector<vector<int>> res;
// vector<int> path;
// vector<vector<int>> combine(int n, int k) {
dfs(n, k, 1);
// return res;
// }
void dfs(int n, int k, int start) { //n代表每层遍历的终点,k代表还需要选取的字母个数,start表示该层开始遍历的起始点。
if (k == 0) {
// res.push_back(path);
// return;
// }
// }
步骤四:填充dfs的内容
// vector<vector<int>> res;
// vector<int> path;
// vector<vector<int>> combine(int n, int k) {
dfs(n, k, 1);
// return res;
// }
// void dfs(int n, int k, int start) { //n代表每层遍历的终点,k代表还需要选取的字母个数,start表示该层开始遍历的起始点。
// if (k == 0) {
// res.push_back(path);
// return;
// }
for (int i = start, i <= n; i++) {
path.push_back(i);
dfs(n, k - 1, i + 1);
path.pop_back(); //回溯
}
// }
257. 二叉树的所有路径(简单)
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:12.9 MB, 在所有 C++ 提交中击败了60.14%的用户
对比77题的path和这里的path的区别。
class Solution {
public:
vector<string> res;
vector<string> binaryTreePaths(TreeNode* root) {
string path;
dfs(root, path);
return res;
}
void dfs(TreeNode* root, string path) {
if (!root) return;
if (path.size()) path += "->";
path += to_string(root->val);
if (!root->left && !root->right) {
res.push_back(path);
return;
}
dfs(root->left, path);
dfs(root->right, path);
}
};
93. 复原IP地址(中等)
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:6.7 MB, 在所有 C++ 提交中击败了80.05%的用户
class Solution {
public:
vector<string> res;
vector<string> restoreIpAddresses(string s) {
string path;
dfs(s, 0, 0, path);
return res;
}
void dfs(string& s, int start, int area, string path) {
if (area == 4) {
if (start == s.size()) res.push_back(path.substr(1));
return;
}
if (s[start] == '0') dfs(s, start + 1, area + 1, path + ".0");
else {
for (int i = start, k = 0; i < s.size(); i++) {
k = k * 10 + s[i] - '0';
if (k < 256) dfs(s, i + 1, area + 1, path + '.' + to_string(k));
else break;
}
}
}
};
步骤一:使用深搜,确定深搜的初始状态以及终止条件
vector<string> res;
vector<string> restoreIpAddresses(string s) {
string path;
dfs(s, 0, 0, path);
return res;
}
void dfs(string& s, int index, int area, string path) {
if (area == 4) {
if (index == s.size()) res.push_back(path.substr(1));
return;
}
}
步骤二:考虑特殊情况,如果该为为0,则这个区域只能有一个0
//vector<string> res;
//vector<string> restoreIpAddresses(string s) {
// string path;
// dfs(s, 0, 0, path);
// return res;
//}
//void dfs(string& s, int index, int area, string path) {
// if (area == 4) {
// if (index == s.size()) res.push_back(path.substr(1));
// return;
// }
if (s[index] == '0') dfs(s, index + 1, area + 1, path + ".0");
//}
步骤三:考虑一般情况的深搜。任何小于256的数都可以作为一个合法的area。
//vector<string> res;
//vector<string> restoreIpAddresses(string s) {
// string path;
// dfs(s, 0, 0, path);
// return res;
//}
//void dfs(string& s, int index, int area, string path) {
// if (area == 4) {
// if (index == s.size()) res.push_back(path.substr(1));
// return;
// }
// if (s[index] == '0') dfs(s, index + 1, area + 1, path + ".0");
// else {
for (int i = index, j = 0; i < s.size(); i++) {
j = j * 10 + s[i] - '0';
if (j < 256) dfs(s, i + 1, area + 1, path + '.' + to_string(j));
else break;
}
}
//}
树
98. 验证二叉搜索树(中等)
执行用时:16 ms, 在所有 C++ 提交中击败了70.75%的用户
内存消耗:21 MB, 在所有 C++ 提交中击败了72.10%的用户
思路:
- 当前结点左边的值都比这个结点的值小,右边的值都比这个结点的值大。
- 所有值的范围在INT_MIN~INT_MAX之间。
- 写一个二叉树形状的递归。
class Solution {
public:
bool isValidBST(TreeNode* root) {
return dfs(root, INT_MIN, INT_MAX);
}
bool dfs(TreeNode*root, long long minv, long long maxv) {
if (!root) return true;
if (root->val > maxv || root->val < minv) return false;
return dfs(root->left, minv, root->val - 1ll) && dfs(root->right, root->val + 1ll, maxv);
}
};
步骤1:做一个深搜,确定深搜的结束条件和参数
bool isValidBST(TreeNode* root) {
return dfs(root, INT_MIN, INT_MAX);
}
void dfs(TreeNode*root, long long minv, long long maxv) {
if (!root) return true;
}
94. 二叉树的中序遍历(中等)
法一:递归
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:8.1 MB, 在所有 C++ 提交中击败了88.37%的用户
class Solution {
public:
vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
dfs(root);
return res;
}
void dfs(TreeNode* root) {
if (!root) return;
dfs(root->left);
res.push_back(root->val);
dfs(root->right);
}
};
步骤一:使用深搜,确定终止条件
vector<int> res;
vector<int> inorderTraversal(TreeNode* root) {
dfs(root);
return res;
}
void dfs(TreeNode* root) {
if (!root) return;
dfs(root->left);
}
步骤二:将该结点的值放入res数组中,再切换到该结点的右支,继续深搜
//vector<int> res;
//vector<int> inorderTraversal(TreeNode* root) {
// dfs(root);
// return res;
//}
//void dfs(TreeNode* root) {
// if (!root) return;
// dfs(root->left);
res.push_back(root->val);
dfs(root->right);
//}
法二:迭代
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:8.1 MB, 在所有 C++ 提交中击败了91.95%的用户
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
TreeNode* p = root;
while (p || stk.size()) {
while (p) {
stk.push(p);
p = p->left;
}
TreeNode* node = stk.top();
stk.pop();
res.push_back(node->val);
p = node->right;
}
return res;
}
};
步骤一:递归的本质是对数据结构栈的操作,因此需要维护一个栈
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> stk;
}
步骤二:用“栈”模拟递归。难点是模拟“深搜”与“回溯”。
//vector<int> inorderTraversal(TreeNode* root) {
// vector<int> res;
// stack<TreeNode*> stk;
while (终止条件) {
//深搜
while (root) {
stk.push(root);
root = root->left;
}
//回溯
root = stk.top();
stk.pop();
//切换分支,继续深搜
root = root-> right;
// }
//}
步骤三:判断递归的终止条件
//vector<int> inorderTraversal(TreeNode* root) {
// vector<int> res;
// stack<TreeNode*> stk;
while (终止条件) {
//深搜
while (root) {
stk.push(root);
root = root->left;
}
//回溯
root = stk.top();
res.push_back(root->val);
stk.pop();
//切换分支,继续深搜
root = root-> right;
// }
//}
步骤四:确定终止条件,返回答案
//vector<int> inorderTraversal(TreeNode* root) {
// vector<int> res;
// stack<TreeNode*> stk;
while (root || stk.size()) {
// while (root) {
// stk.push(root);
// root = root->left;
// }
// root = stk.top();
// res.push_back(root->val);
// stk.pop();
// root = root->right;
// }
return res;
//}
101. 对称二叉树(简单)
执行用时:4 ms, 在所有 C++ 提交中击败了84.84%的用户
内存消耗:15.9 MB, 在所有 C++ 提交中击败了20.64%的用户
难点:如何深搜
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) return true;
return dfs(root->left, root->right);
}
bool dfs(TreeNode* p, TreeNode* q) {
if (!p && q || p &&!q) return false;
if (!p && !q) return true;
return p->val == q->val && dfs(p->left, q->right) && dfs(p->right, q->left);
}
};
步骤一:确定深搜初始条件
bool isSymmetric(TreeNode* root) {
if (!root) return true;
return dfs(root->left, root->right);
}
bool dfs(TreeNode* p, TreeNode* q) {}
步骤二:得出深搜终止条件
//bool isSymmetric(TreeNode* root) {
// if (!root) return true;
// return dfs(root->left, root->right);
//}
//bool dfs(TreeNode* p, TreeNode* q) {
if (!p && !q) return true;
if (p && !q || !p && q) return false;
if (p->val != q->val) return false;
//}
步骤三:考虑深搜的方法:从根结点的左右子节点开始,左结点的左儿子等于右结点的右儿子,左结点的右儿子等于右结点的左儿子
//bool isSymmetric(TreeNode* root) {
// if (!root) return true;
// return dfs(root->left, root->right);
//}
//bool dfs(TreeNode* p, TreeNode* q) {
// if (!p && !q) return true;
// if (p && !q || !p && q) return false;
// if (p->val != q->val) return false;
return dfs(p->left, q->right) && dfs(p->right, q->left);
//}
迭代法:类比迭代法进行中序遍历的操作
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:16 MB, 在所有 C++ 提交中击败了9.01%的用户
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) return true;
stack<TreeNode*> l_stk, r_stk;
TreeNode* l_root = root->left, * r_root = root->right;
while (l_root || r_root || l_stk.size() || r_stk.size()) {
while (l_root && r_root) {
l_stk.push(l_root), r_stk.push(r_root);
l_root = l_root->left, r_root = r_root->right;
}
if (l_root || r_root) return false;
l_root = l_stk.top(), r_root = r_stk.top();
l_stk.pop(), r_stk.pop();
if (l_root->val != r_root->val) return false;
l_root = l_root->right, r_root = r_root->left;
}
return true;
}
};
105. 从前序与中序遍历序列构造二叉树(中等)
102. 二叉树的层序遍历(中等)
使用队列:
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.05%的用户
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) return res;
queue<pair<TreeNode*, int>>q;
q.push(make_pair(root, 0));
while (q.size()) {
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if (res.size() <= level) res.push_back({});
res[level].push_back(node->val);
if (node->left) q.push(make_pair(node->left, level + 1));
if (node->right) q.push(make_pair(node->right, level + 1));
}
return res;
}
};
执行用时:4 ms, 在所有 C++ 提交中击败了84.05%的用户
内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.46%的用户
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (q.size()) {
int len = q.size();
vector<int> level;
for (int i = 0; i < len; i++) {
TreeNode* node = q.front();
q.pop();
level.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
res.push_back(level);
}
return res;
}
};
n false;
return dfs(p->left, q->right) && dfs(p->right, q->left);
//}
迭代法:类比迭代法进行中序遍历的操作
>执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
>
>内存消耗:16 MB, 在所有 C++ 提交中击败了9.01%的用户
~~~cpp
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root) return true;
stack<TreeNode*> l_stk, r_stk;
TreeNode* l_root = root->left, * r_root = root->right;
while (l_root || r_root || l_stk.size() || r_stk.size()) {
while (l_root && r_root) {
l_stk.push(l_root), r_stk.push(r_root);
l_root = l_root->left, r_root = r_root->right;
}
if (l_root || r_root) return false;
l_root = l_stk.top(), r_root = r_stk.top();
l_stk.pop(), r_stk.pop();
if (l_root->val != r_root->val) return false;
l_root = l_root->right, r_root = r_root->left;
}
return true;
}
};
105. 从前序与中序遍历序列构造二叉树(中等)
102. 二叉树的层序遍历(中等)
使用队列:
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.05%的用户
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) return res;
queue<pair<TreeNode*, int>>q;
q.push(make_pair(root, 0));
while (q.size()) {
TreeNode* node = q.front().first;
int level = q.front().second;
q.pop();
if (res.size() <= level) res.push_back({});
res[level].push_back(node->val);
if (node->left) q.push(make_pair(node->left, level + 1));
if (node->right) q.push(make_pair(node->right, level + 1));
}
return res;
}
};
执行用时:4 ms, 在所有 C++ 提交中击败了84.05%的用户
内存消耗:12.3 MB, 在所有 C++ 提交中击败了9.46%的用户
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (q.size()) {
int len = q.size();
vector<int> level;
for (int i = 0; i < len; i++) {
TreeNode* node = q.front();
q.pop();
level.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
res.push_back(level);
}
return res;
}
};