此为我在2022/4/24(文件最后修改日期)前写的一些leetcode刷题笔记,对应leetbook中《初级算法》一书。放在CSDN作为备份,方便后续复习回顾用。
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
提示:
1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
sort (nums1.begin(), nums1.end())
while (双指针终止条件) { ... }
的写法class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
//排序
sort (nums1.begin(), nums1.end());
sort (nums2.begin(), nums2.end());
vector<int> res;
int n1 = nums1.size();
int n2 = nums2.size();
//双指针
int i = 0, j = 0;
while (i < n1 && j < n2) { //不能只会用for循环,双指针可以用while里面放停止条件的写法
if (nums1[i] < nums2[j]) i++; //因为是有序数组,i和j谁的数小谁往前走,直到二者相等
else if (nums1[i] > nums2[j]) j++; //相等时将该值存到结果数组中,i和j再一起往前走一步
else {
res.push_back(nums1[i]);
i++;
j++;
}
}
return res;
}
};
unordered_map
内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。unordered_map
时,需要引入头文件:#include < unordered_map >
unordered_map
因为内部实现了哈希表,因此其查找速度非常的快map
对比,对于那些有顺序要求的问题,用map
会更高效一些,对于查找问题,常会考虑一下用unordered_map
class Solution {
public:
//由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数
//对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) { //为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数
return intersect(nums2, nums1); //然后再遍历较长的数组得到交集
}
unordered_map <int, int> m;
for (int num : nums1) { //首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数
++m[num];
}
vector<int> res;
for (int num : nums2) { //遍历第二个数组,对于第二个数组中的每个数字
if (m.count(num)) { //如果在哈希表中存在这个数字
res.push_back(num); //则将该数字添加到答案
--m[num]; //并减少哈希表中该数字出现的次数
if (m[num] == 0) {
m.erase(num); //如果次数减至0,则在哈希表中删除该值
}
}
}
return res;
}
};
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
提示:
1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1
vector.erase()
的使用class Solution {
public:
void moveZeroes(vector<int>& nums) {
int n = nums.size();
auto it = nums.begin();
int cnt = 0;
while (it != nums.end()) {
if (*it == 0) {
cnt++;
it = nums.erase(it); //要特别注意这种写法,删除后迭代器指向的是原来删除元素的后一个元素,所以不要多加it++
} else {
it++;
}
}
while (cnt--) nums.push_back(0);
}
};
class Solution {
public void moveZeroes(int[] nums) {
int index = 0;
for (int num : nums) {
if (num != 0) {
nums[index++] = num; //符合题意直接覆盖就行
}
}
for (int i = index; i < nums.length; i++) {
nums[i] = 0;
}
}
}
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> res;
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
if (nums[j] == target - nums[i]) {
res.push_back(i); //res = vector({i, j});
res.push_back(j);
return res;
}
}
}
return res;
}
};
unordered_map hash
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
//循环一遍 numsnums 数组,在每步循环中我们做两件事:
//1.判断 target−nums[i]target−nums[i] 是否在哈希表中
//2.将 nums[i]nums[i] 插入哈希表中
vector<int> res;
unordered_map<int,int> hash;
for (int i = 0; i < nums.size(); i ++ )
{
int another = target - nums[i];
if (hash.count(another))
{
res = vector<int>({hash[another], i}); //注意直接给vecto赋值的写法vector({数组各值,逗号隔开})
break; //用push_back()也行
}
hash[nums[i]] = i; //键值对为值和下标,hash[值] = 下标
}
return res;
}
};
matrix_new[j][n - i - 1] = matrix[i][j]
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// C++ 这里的 = 拷贝是值拷贝,会得到一个新的数组
auto matrix_new = matrix;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
matrix_new[j][n - i - 1] = matrix[i][j];
}
}
// 这里也是值拷贝
matrix = matrix_new;
}
};
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
for (int i = 0; i < n; i ++ ) //先以左上-右下对角条线为轴做翻转;
for (int j = i + 1; j < n; j ++ )
swap(matrix[i][j], matrix[j][i]);
for (int i = 0; i < n; i ++ ) //再以中心的竖线为轴做翻转;
for (int j = 0, k = n - 1;
j < k; j ++, k -- )
swap(matrix[i][j], matrix[i][k]);
}
};
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−2^31, 2^31 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)。
示例 1:
输入:x = 123
输出:321
示例 2:
输入:x = -123
输出:-321
示例 3:
输入:x = 120
输出:21
示例 4:
输入:x = 0
输出:0
提示:
-2^31 <= x <= 2^31 - 1
−12%10=−2−12%10=−2
,所以我们不需要对负数进行额外处理INT_MIN
和INT_MAX
的运用,要注意防止溢出2147483647
,是21亿的数量级,比它小的数反转之后可能会溢出,要额外小心class Solution {
public:
int reverse(int x) {
int rev = 0;
while (x != 0) {
if (rev < INT_MIN / 10 || rev > INT_MAX / 10) {
return 0;
}
int digit = x % 10;
x /= 10;
rev = rev * 10 + digit;
}
return rev;
}
};
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
提示:
1 <= s.length <= 10^5
s 只包含小写字母
class Solution {
public:
int firstUniqChar(string s) {
unordered_map <char, int> hash;
for (auto x : s) {
hash[x]++;
}
for (int i = 0; i < s.size(); i++) {
if (hash[s[i]] == 1) {
return i;
}
}
return -1;
}
};
//简化版
class Solution {
public:
int firstUniqChar(string s) {
unordered_map<char, int> hash;
for (auto c: s) hash[c] ++ ;
for (int i = 0; i < s.size(); i ++ )
if (hash[s[i]] == 1)
return i;
return -1;
}
};
class Solution {
public:
int fre[26];
int firstUniqChar(string s) {
for (int i = 0; i < s.size(); i++) {
fre[s[i] - 'a']++;
}
for (int i = 0; i < s.size(); i++) {
if (fre[s[i] - 'a'] == 1) {
return i;
}
}
return -1;
}
};
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 10^4
s 和 t 仅包含小写字母
class Solution {
public:
bool isAnagram(string s, string t) {
unordered_map<char, int> hash_s;
unordered_map<char, int> hash_t;
for (auto x : s) {
hash_s[x]++;
}
for (auto x : t) {
hash_t[x]++;
}
int n1 = hash_s.size();
int n2 = hash_t.size();
if (n1 != n2) return false;
for (int i = 0; i < s.size(); i++) {
if (hash_s[s[i]] != hash_t[s[i]]) return false;
}
return true;
}
};
//简化版
class Solution {
public:
bool isAnagram(string s, string t) {
unordered_map<char, int> a, b;
for (auto c: s) a[c] ++ ;
for (auto c: t) b[c] ++ ;
return a == b; //这里提示我们哈希表是可以直接比较的
}
};
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
sort(s.begin(), s.end());
sort(t.begin(), t.end());
return s == t;
}
};
vector table(26, 0);
学会这种初始化一维数组的办法class Solution {
public:
bool isAnagram(string s, string t) {
if (s.length() != t.length()) {
return false;
}
vector<int> table(26, 0);
for (auto& ch: s) {
table[ch - 'a']++;
}
for (auto& ch: t) {
table[ch - 'a']--;
if (table[ch - 'a'] < 0) {
return false;
}
}
return true;
}
};
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
解释:"amanaplanacanalpanama" 是回文串
示例 2:
输入: "race a car"
输出: false
解释:"raceacar" 不是回文串
提示:
1 <= s.length <= 2 * 10^5
字符串 s 由 ASCII 字符组成
tolower()
**将字符转成小写, 非字母字符不做出处理isalnum()
**判断是否为数字和字母,太妙了isalpha()
**用来判断一个字符是否为字母,如果是字符则返回非零,否则返回零isdigit()
**函数判断字符是否是数字islower()
用来判断一个字符是否为小写字母,也就是是否属于a~zisupper()
和islower()
相反,用来判断一个字符是否为大写字母class Solution {
public:
bool isPalindrome(string s) {
string sgood;
for (char ch: s) {
if (isalnum(ch)) {
sgood += tolower(ch);
}
}
string sgood_rev(sgood.rbegin(), sgood.rend());
return sgood == sgood_rev;
}
};
class Solution {
public:
bool isPalindrome(string s) {
string sgood;
for (char ch: s) {
if (isalnum(ch)) {
sgood += tolower(ch);
}
}
int n = sgood.size();
int left = 0, right = n - 1;
while (left < right) {
if (sgood[left] != sgood[right]) {
return false;
}
++left;
--right;
}
return true;
}
};
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1:
输入:haystack = "hello", needle = "ll"
输出:2
示例 2:
输入:haystack = "aaaaa", needle = "bba"
输出:-1
示例 3:
输入:haystack = "", needle = ""
输出:0
提示:
1 <= haystack.length, needle.length <= 10^4
haystack 和 needle 仅由小写英文字符组成
find()
快速解决这题,笔试面试如果能这样AC就直接AC,不行就用KMP算法class Solution {
public:
int strStr(string haystack, string needle) {
return haystack.find(needle);
}
};
class Solution {
public:
int strStr(string s, string p) {
if (p.empty()) return 0;
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p; //加一个空字符,让后面的遍历从1开始
vector<int> next(m + 1);
for (int i = 2, j = 0; i <= m; i ++ ) { //求模式串的next数组
while (j && p[i] != p[j + 1]) j = next[j];
if (p[i] == p[j + 1]) j ++ ;
next[i] = j;
}
for (int i = 1, j = 0; i <= n; i ++ ) { //匹配
while (j && s[i] != p[j + 1]) j = next[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m) return i - m;
}
return -1;
}
};
给定一个正整数 n ,输出外观数列的第 n 项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = "1"
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。
前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
class Solution {
public:
string countAndSay(int n) {
string s = "1";
for (int i = 1; i < n; i++) {
string t;
for (int j = 0; j < s.size(); ) {
int k = j + 1;
while (k < s.size() && s[k] == s[j]) k++;
t += to_string(k - j) + s[j];
j = k;
}
s = t;
}
return s;
}
};
即将所有30种结果枚举出来,存在一个数组当中(不推荐)
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 仅由小写英文字母组成
i >= x.size() || x[i] != c
时要终止class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string res;
if (strs.empty()) return res;
for (int i = 0; i < strs[0].size(); i++) {
char c = strs[0][i];
for (auto x : strs) {
if (i >= x.size() || x[i] != c)
return res;
}
res += c;
}
return res;
}
};
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (!strs.size()) {
return "";
}
string prefix = strs[0];
int count = strs.size();
for (int i = 1; i < count; ++i) {
prefix = longestCommonPrefix(prefix, strs[i]);
if (!prefix.size()) {
break;
}
}
return prefix;
}
string longestCommonPrefix(const string& str1, const string& str2) {
int length = min(str1.size(), str2.size());
int index = 0;
while (index < length && str1[index] == str2[index]) {
++index;
}
return str1.substr(0, index);
}
};
请编写一个函数,用于 删除单链表中某个特定节点 。在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点 。
题目数据保证需要删除的节点 不是末尾节点 。
输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
给你一个链表,删除链表的倒数第 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]
/**
* 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) {
//先添加一个哑节点,有助于统一情况,使得head也有前驱结点
auto dummy = new ListNode(-1, head); //auto dummy = new ListNode(-1);dummy->next = head;
//计算链表长度
int len = 0;
while (head) {
head = head->next;
len++;
}
//定位到要删除结点的前驱
auto p = dummy;
for (int i = 0; i < len - n; i++) {
p = p->next;
}
//删除操作
p->next = p->next->next;
// ListNode* ans = dummy->next;
// delete dummy;
// return ans;
return dummy->next; //只是解题的话,不用delete dummy也没关系
}
};
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy = new ListNode(0, head);
stack<ListNode*> stk; //构造一个存放ListNode*结点的栈
ListNode* cur = dummy;
while (cur) {
stk.push(cur); //所有元素进栈
cur = cur->next;
}
for (int i = 0; i < n; ++i) {
stk.pop(); //倒数n个元素全出栈,停止
}
ListNode* prev = stk.top(); //那么下一个出栈的就是倒数第n个结点的前驱
prev->next = prev->next->next;
return dummy->next;
}
};
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
ListNode* pre = nullptr;
不能写成auto pre = nullptr;
Line 21: Char 19: error: assigning to 'nullptr_t' from incompatible type 'ListNode *'
pre = p; //pre更改为为当前结点,下一个结点就能指过来
^
1 error generated.
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//遍历一遍这个链表
//为了不丢失下个结点的信息,在操作结点时要把其next先保存起来
ListNode* pre = nullptr; //初始化空结点,表示反转后最后一个元素指为空
auto p = head;
while (p) {
auto q = p->next; //要操作p,先把原本的next存起来让p能正常遍历下去
p->next = pre;
pre = p; //pre更改为为当前结点,下一个结点就能指过来
p = q;
}
return pre; //pre最后会指向原链表最后的结点,而p会变成null终止循环
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) {
return head;
}
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
};
此处内容在寝室电脑
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 10^4]
-10^5 <= Node.val <= 10^5
pos 为 -1 或者链表中的一个有效索引 。
set
而不是map
,是用insert()
而不是push_back()class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode*> hash; //这里也要注意unordered_set hash而不是unordered_set hash
//遍历一遍链表,如果读取的结点在哈希表里,说明访问过,存在环,不在哈希表则放入哈希表中
while (head) {
if (hash.count(head)) return true;
hash.insert(head);
head = head->next;
}
return false;
}
};
class Solution {
public:
bool hasCycle(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return false;
}
ListNode* slow = head;
ListNode* fast = head->next;
while (slow != fast) {
if (fast == nullptr || fast->next == nullptr) {
return false;
}
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
if (!root->left && !root->right) return 1;
也就是找到叶子结点时为递归出口if (root->left)
和if (root->right)
两种情况,存在左右结点说明深度也要加1,用1 + maxDepth(root->left);
和1 + maxDepth(root->right);
找到递归出口,这样就算出了左右子树的最大深度,再取最大值即可/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) return 0;
if (!root->left && !root->right) return 1;
int res1 = 0;
int res2 = 0;
if (root->left) res1 = 1 + maxDepth(root->left);
if (root->right) res2 = 1 + maxDepth(root->right);
return max(res1, res2);
}
};
l
和 r
,那么该二叉树的最大深度即为max(l,r)+1
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == nullptr) return 0;
queue<TreeNode*> Q;
Q.push(root);
int ans = 0;
while (!Q.empty()) {
int sz = Q.size();
while (sz > 0) {
TreeNode* node = Q.front();Q.pop();
if (node->left) Q.push(node->left);
if (node->right) Q.push(node->right);
sz -= 1;
}
ans += 1; //遍历完一层后ans加1
}
return ans;
}
};
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
树中节点数目范围在[1, 104] 内
-2^31 <= Node.val <= 2^31 - 1
LLONG_MIN
class Solution {
public:
long long last = LLONG_MIN; //下限值,初始为最小值,放在外面作用全局
bool isValidBST(TreeNode* root) {
if(!root) return true;
if(!isValidBST(root->left)) return false; //判断左子树
if(root->val <= last) return false; //判断当前
last = root->val; //当前值存入last,为右子树的下限
if(!isValidBST(root->right)) return false;
return true;
}
};
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> stack;
long long inorder = (long long)INT_MIN - 1; //另一种表示方法
while (!stack.empty() || root != nullptr) {
while (root != nullptr) {
stack.push(root);
root = root -> left;
}
root = stack.top();
stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root -> val <= inorder) {
return false;
}
inorder = root -> val;
root = root -> right;
}
return true;
}
};
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return !root || dfs(root->left, root->right);
}
bool dfs(TreeNode*p, TreeNode*q)
{
if (!p || !q) return !p && !q;
return p->val == q->val && dfs(p->left, q->right) && dfs(p->right, q->left);
}
};
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-10^9 <= nums1[i], nums2[j] <= 10^9
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> res;
int i = 0, j = 0;
while (i < m && j < n) {
if (nums1[i] <= nums2[j]) {
res.push_back(nums1[i]);
i++;
} else {
res.push_back(nums2[j]);
j++;
}
}
while (i < m) {
res.push_back(nums1[i]);
i++;
}
while (j < n) {
res.push_back(nums2[j]);
j++;
}
nums1 = res;
}
};
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
sort(nums1.begin(), nums1.end());
}
};
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例 1:
输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
提示:
1 <= bad <= n <= 2^31 - 1
int mid = (left + right) / 2;
写成int mid = left + (right - left) / 2;
,这种写法值得记录mid
和mid+1
为界,所以mid = (left + right) / 2
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left < right) {
//int mid = (left + right) / 2;
int mid = left + (right - left) / 2; // 防止计算时溢出
if (isBadVersion(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
class Solution {
public:
int climbStairs(int n) {
int a = 1, b = 2;
if (n == 1) return 1;
if (n == 2) return 2;
int sum = 0;
for (int i = 3; i <= n; i++ ) {
sum = a + b;
a = b;
b = sum;
}
return sum;
}
};
class Solution {
public:
int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
};
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:
输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 400
dp[i]
;dp[i]
:dp[i]
表示表示前 i 间房屋能偷窃到的最高总金额i - 2
家和 i
家,要么偷前 i - 1
家不偷 i
家,取两种可能偷法的最大值
dp[i] = max (dp[i - 2] + nums[i], dp[i - 1]);
i - 2
,很容易直到边界的 i
为 0 和 1
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
if (n == 1) return nums[0];
和 if (n == 2) return max(nums[0], nums[1]);
dp[0]
和dp[1]
就能保证数组不越界if (n == 1) return nums[0];
,那么输入的数组长度为 1 时,计算dp[1]
会越界而报错class Solution {
public:
int rob(vector<int>& nums) {
if (nums.empty()) return 0; //空数组,无家可偷,返回0
int n = nums.size(); //经典记录下数组长度
if (n == 1) return nums[0]; //不用靠dp数组来判断的边界我们直接返回
if (n == 2) return max(nums[0], nums[1]);
vector<int> dp = vector<int>(n, 0); //初始化dp数组为0;
dp[0] = nums[0]; //对应直接返回的情况的值
dp[1] = max(nums[0], nums[1]); //dp方程算不到的初始值先赋值
for (int i = 2; i < n; i++) { //两个房子以上用dp数组算
dp[i] = max (dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[n - 1];
}
};
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
示例 1:
输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0
输出:0
示例 3:
输入:n = 1
输出:0
提示:
0 <= n <= 5 * 10^6
i * i < n
挨个来判断它是否是素数class Solution {
public:
int countPrimes(int n) {
int cnt = 0;
for (int i = 0; i < n; i++) {
if (isP(i)) cnt++;
}
return cnt;
}
bool isP (int x) {
if (x == 0 || x == 1) return false;
for (int i = 2; i * i <= x; i++) {
if (x % i == 0) return false;
}
return true;
}
};
class Solution {
public:
int countPrimes(int n) {
if (n < 2) return 0;
int cnt = 0;
vector<bool> st = vector<bool>(n, false); //初始化一个st数组,用来记录该数是否被筛
for (int i = 2; i < n; i++) {
if (st[i]) continue; //该数未被筛,cnt++
cnt++; //这里计算素数个数用cnt,若要得到素数数组则另开一个数组存放这个数
for (int j = 2 * i; j < n; j += i)
st[j] = true; //开筛开筛
}
return cnt;
}
};
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3^x
示例 1:
输入:n = 27
输出:true
示例 2:
输入:n = 0
输出:false
示例 3:
输入:n = 9
输出:true
示例 4:
输入:n = 45
输出:false
提示:
-2^31 <= n <= 2^31 - 1
class Solution {
public:
bool isPowerOfThree(int n) {
while (n && n % 3 == 0) {
n /= 3;
}
if (n == 1) return true;
else return false;
}
};
class Solution {
public:
bool isPowerOfThree(int n) {
return n > 0 && 1162261467 % n == 0;
}
};
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III"
输出: 3
示例 2:
输入: s = "IV"
输出: 4
示例 3:
输入: s = "IX"
输出: 9
示例 4:
输入: s = "LVIII"
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV"
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
1 <= s.length <= 15
s 仅含字符 ('I', 'V', 'X', 'L', 'C', 'D', 'M')
题目数据保证 s 是一个有效的罗马数字,且表示整数在范围 [1, 3999] 内
题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
关于罗马数字的详尽书写规则,可以参考 罗马数字 - Mathematics 。
class Solution {
private:
unordered_map<char, int> symbolValues = { //简历unordered_map对应表
{'I', 1},
{'V', 5},
{'X', 10},
{'L', 50},
{'C', 100},
{'D', 500},
{'M', 1000},
};
public:
int romanToInt(string s) {
int ans = 0;
int n = s.length();
for (int i = 0; i < n; ++i) {
int value = symbolValues[s[i]];
if (i < n - 1 && value < symbolValues[s[i + 1]]) { //按照规则进行加或减,不要想复杂了
ans -= value;
} else {
ans += value;
}
}
return ans;
}
};
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。
提示:
输入必须是长度为 32 的 二进制串 。
lowbit(n) = n & -n
class Solution {
public:
int hammingWeight(uint32_t n) {
int res = 0;
while (n) n -= n & -n, res ++ ;
return res;
}
};
n >> k & 1
class Solution {
public:
int hammingWeight(uint32_t n) {
int res = 0;
for (int i = 0; i < 32; i ++ )
res += n >> i & 1;
return res;
}
};
n & (n−1)
,其运算结果恰为把 n的二进制位中的最低位的 1 变为 0 之后的结果class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n) {
n &= n - 1;
ret++;
}
return ret;
}
};
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
示例 1:
输入:x = 1, y = 4
输出:2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑
上面的箭头指出了对应二进制位不同的位置。
示例 2:
输入:x = 3, y = 1
输出:1
提示:
0 <= x, y <= 2^31 - 1
__builtin_popcount
用来表示二进制表示的1的个数class Solution {
public:
int hammingDistance(int x, int y) {
//也就是求X和y异或之后的1的个数
int n = x ^ y;
int res = 0;
//方法1 lowbits算,返回最后一个1,不断减去即可
// while (n) {
// n -= n & -n;
// res++;
// }
//方法2 不断移位去求1的个数
// while (n) {
// res += n & 1;
// n >>= 1;
// }
//方法3 内置函数法
res = __builtin_popcount(n);
//方法4 将最后一个1换0看能换多少次
// while (n) {
// n &= n - 1;
// res++;
// }
return res;
}
};
颠倒给定的 32 位无符号整数的二进制位。
提示:
请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825。
示例 1:
输入:n = 00000010100101000001111010011100
输出:964176192 (00111001011110000010100101000000)
解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596,
因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000。
示例 2:
输入:n = 11111111111111111111111111111101
输出:3221225471 (10111111111111111111111111111111)
解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。
提示:
输入是一个长度为 32 的二进制字符串
n >> k & 1
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
uint32_t res = 0;
for (int i = 0; i < 32; i++) {
res = (res << 1) + (n >> i & 1);
}
return res;
}
};
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
示例 2:
输入: numRows = 1
输出: [[1]]
提示:
1 <= numRows <= 30
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
for (int i = 0; i < numRows; i++) {
vector<int> temp = vector<int>(i+1, 1); //要记住这种初始化vector的方法,这里初始化为1
if (i >= 2) { //前两行都是1,从第三行开始杨辉三角计算中间部分的值
for (int j = 1; j < i; j++) {
temp[j] = res[i - 1][j - 1] + res[i - 1][j];
}
}
res.push_back(temp);
}
return res;
}
};
class Solution {
public:
bool isValid(string s) {
stack<char> stk;
int n = s.size();
for (int i = 0; i < n; i++) { //可以用auto简写
if (s[i] == '(' || s[i] == '{' || s[i] == '[') {
stk.push(s[i]);
}
else if (s[i] == ')') {
if (!stk.empty() && stk.top() == '(') stk.pop(); //先判断!stk.empty()
else return false;
}
else if (s[i] == ']') {
if (!stk.empty() && stk.top() == '[') stk.pop();
else return false;
}
else if (s[i] == '}') {
if (!stk.empty() && stk.top() == '{') stk.pop();
else return false;
}
}
return stk.empty();
}
};
class Solution {
public:
bool isValid(string s) {
//用栈来做
stack<char> stk;
for (auto c : s) {
if (c == '(' || c == '[' || c == '{') stk.push(c);
else if (c == ')') {
//要特别注意先判断stk.empty(),若stk.empty()为1表示栈空,直接返回false,不判断后面的stk.top() != '('
//如果写法为stk.top() != '(' || stk.empty() ,那么在输入为")"时执行stk.top()会报错,因为此时为空栈!
if (stk.empty() || stk.top() != '(') return false;
stk.pop();
} else if (c == ']') {
if (stk.empty() || stk.top() != '[') return false;
stk.pop();
} else {
if (stk.empty() || stk.top() != '{') return false;
stk.pop();
}
}
return stk.empty();
}
};
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
示例 1:
输入:nums = [3,0,1]
输出:2
解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1]
输出:2
解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1]
输出:8
解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0]
输出:1
解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
提示:
n == nums.length
1 <= n <= 104
0 <= nums[i] <= n
nums 中的所有数字都 独一无二
class Solution {
public:
int missingNumber(vector<int>& nums) {
sort (nums.begin(), nums.end());
int n = nums.size();
int i;
for (i = 0; i < n; i++) {
if (nums[i] != i) return i;
}
return i;
}
};
练习一下哈希表的使用
把存在的值放入哈希表,再把不存在的值找出来
class Solution {
public:
int missingNumber(vector<int>& nums) {
unordered_set<int> set;
int n = nums.size();
for (int i = 0; i < n; i++) {
set.insert(nums[i]);
}
int missing = -1;
for (int i = 0; i <= n; i++) {
if (!set.count(i)) {
missing = i;
break;
}
}
return missing;
}
};
1^1^2^2^3 = 3
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = 0;
int n = nums.size();
for (int i = 0; i < n; i++) {
res ^= nums[i];
}
for (int i = 0; i <= n; i++) {
res ^= i;
}
return res;
}
};
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector> levelOrder(TreeNode* root) {
vector> res;
if (!root) return res; //养成习惯,先判空
queue q; //层序遍历用队列来做
q.push(root);
while (!q.empty()) {
vector level;
int n = q.size();
while (n--) { //遍历队列中的结点,依次读它们的子结点
auto t = q.front(); //用t指向队首结点
q.pop(); //指完后出栈
level.push_back(t->val); //出栈后保存结点值
if (t->left) q.push(t->left); //如果有子结点,也要入栈
if (t->right) q.push(t->right);
}
res.push_back(level);
}
return res;
}
};
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例 1:
输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案
示例 2:
输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3] 和 [3,1] 都是高度平衡二叉搜索树。
提示:
1 <= nums.length <= 10^4
-10^4 <= nums[i] <= 10^4
nums 按 严格递增 顺序排列
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return build(nums, 0, nums.size() - 1);
}
TreeNode* build(vector<int> &nums, int l, int r) {
if (l > r) return NULL;
int mid = (l + r) >> 1;
auto root = new TreeNode(nums[mid]);
root->left = build (nums, l, mid -1);
root->right = build (nums, mid + 1, r);
return root;
}
};