数组问题
买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0)
return 0;
int max = 0;
for (int i = 0; i < prices.size()-1; i++)
{
if (prices[i + 1] - prices[i] <= 0)
{
continue;
}
else
{
int temp = prices[i + 1] - prices[i];
max = max + temp;
}
}
return max;
}
};
题解:
假设给定的数组为:
[7, 1, 5, 3, 6, 4]
如果我们在图表上绘制给定数组中的数字,我们将会得到:
如果我们分析图表,那么我们的兴趣点是连续的峰和谷。
用数学语言描述为:
T
o
t
a
l
P
r
o
f
i
t
=
∑
i
(
h
e
i
g
h
t
(
p
e
a
k
i
)
−
h
e
i
g
h
t
(
v
a
l
l
e
y
i
)
)
TotalProfit= \sum_{i} ( height ( peak_{i} )- height(valley_{i} ) )
TotalProfit=∑i(height(peaki)−height(valleyi))
关键是我们需要考虑到紧跟谷的每一个峰值以最大化利润。如果我们试图跳过其中一个峰值来获取更多利润,那么我们最终将失去其中一笔交易中获得的利润,从而导致总利润的降低。
试图通过考虑差异较大的点以获取更多的利润,获得的净利润总是会小与包含它们而获得的静利润,因为 C 总是小于 A+B。
作者:LeetCode
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/solution/mai-mai-gu-piao-de-zui-jia-shi-ji-ii-by-leetcode/
存在重复元素
class Solution1 {
public:
bool containsDuplicate(vector<int>& nums) {
if(nums.size()==0)
return 0;
auto beg = nums.begin();
auto end = nums.end();
sort(beg, end);
for (int i = 0; i < nums.size() - 1; ++i) {
if (nums[i] == nums[i + 1])
return true;
}
return false;
}
};
class Solution2 {
public:
bool containsDuplicate(vector<int>& nums) {
auto beg = nums.begin();
auto end = nums.end();
for (auto x : nums)
{
if (count(beg, end, x) > 1)
{
return true;
}
}
return false;
}
};
class Solution3 {
public:
bool containsDuplicate(vector<int>& nums) {
set<int> temp;
for (auto x : nums)
{
if (temp.find(x)==temp.end())
{
temp.insert(x);
}
else
{
return true;
}
}
return false;
}
};
只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
class Solution1 { //使用标准库函数
public:
int singleNumber(vector<int>& nums) {
for (int x : nums)
{
if (count(nums.begin(), nums.end(), x) == 1)
{
return x;
}
}
return 0;
}
};
数学方法:
2
∗
(
a
+
b
+
c
)
−
(
a
+
a
+
b
+
b
+
c
)
=
c
2*(a+b+c)-(a+a+b+b+c)=c
2∗(a+b+c)−(a+a+b+b+c)=c
class Solution2 { //使用数学方法
public:
int singleNumber(vector<int>& nums) {
set<int>temp;
for (auto x : nums)
temp.insert(x);
auto sum0 = accumulate(nums.begin(), nums.end(), 0);
auto sum1 = accumulate(temp.begin(), temp.end(),0);
return 2 * sum1 - sum0;
}
};
两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> result;
map<int, int> temp;
if(nums1.size()>nums2.size())
{
swap(nums1, nums2);
}
for (auto x : nums1)
{
//temp.insert({ x,count(nums1.begin(),nums1.end(),x) }); //调用库函数直接得到结果,但是花销有点大
if (!temp.count(x))
{
temp.insert({ x,1 });
}
else
{
temp[x]++;
}
}
for (auto x : nums2)
{
if (temp[x] != 0)
{
result.push_back(x);
temp[x]--;
}
}
return result;
}
};
两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
class Solution1 {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>result;
for (int i=0;i<nums.size();i++)
{
auto it = find(nums.begin(), nums.end(), target - nums[i]); //使用库函数
if (it == nums.end())
{
continue;
}
else
{
int j = 0;
for (auto beg = nums.begin(); beg != it; beg++)
{
j++;
}
if (i == j)
{
continue;
}
else
{
result.push_back(i);
result.push_back(j);
break;
}
}
}
return result;
}
};
class Solution2 {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>result;
map<int, int>temp; //使用哈希表, {值,数组的下标}
for (int i=0;i<nums.size();i++)
{
temp.insert({ nums[i],i });
}
for (int i=0;i<nums.size();i++)
{
int x = target - nums[i];
if (temp.count(x) && (temp[x]!=i))
{
result.push_back(i);
result.push_back(temp[x]);
break;
}
}
return result;
}
};
有效的数独
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
子数独的标记 box_index = (row / 3) * 3 + columns / 3 为该题要点
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
//横向
for (auto x : board)
{
set<char> temp;
for (auto y : x)
{
if (!temp.count(y))
{
temp.insert(y);
}
else if (y == '.')
{
continue;
}
else
{
return false;
}
}
}
//竖向
for (int i = 0; i < board.size(); i++)
{
set<char> temp;
for (int j = 0; j < board.size(); j++)
{
if (!temp.count(board[j][i]))
{
temp.insert(board[j][i]);
}
else if (board[j][i] == '.')
{
continue;
}
else
{
return false;
}
}
}
//九格
map<char, int> mp[9];
for (int i = 0; i < board.size(); i++)
{
for (int j = 0; j < board.size(); j++)
{
int box_index = (i / 3) * 3 + (j / 3);
if (!mp[box_index].count(board[i][j]) && board[i][j] != '.')
{
mp[box_index].insert({ board[i][j] ,box_index });
}
else if(mp[box_index][board[i][j]]== box_index && board[i][j] != '.')
{
return false;
}
else if (board[i][j] == '.')
{
continue;
}
}
}
return true;
}
};
旋转图像
给定一个 n × n 的二维矩阵表示一个图像。
将图像顺时针旋转 90 度。
说明:
你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。
重点在于先转置,然后翻转每一行
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
for (int i = 0; i < matrix.size(); i++)
{
for (int j = 0; j < matrix[0].size(); j++)
{
if (i == j)
{
continue;
}
else if(i>j)
{
swap(matrix[i][j], matrix[j][i]);
}
}
}
for (int i = 0; i < matrix.size(); i++)
{
for (int j = 0; j < matrix[0].size()/2; j++)
{
swap(matrix[i][j], matrix[i][matrix.size() - j-1]);
}
}
}
};
字符串问题
整数反转
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为
[
−
2
31
,
2
31
−
1
]
[-2^{31},2^{31}-1]
[−231,231−1]。请根据这个假设,如果反转后整数溢出那么就返回 0。
要在没有辅助堆栈 / 数组的帮助下 “弹出” 和 “推入” 数字,我们可以使用数学方法。
//pop operation:
pop = x % 10;
x /= 10;
//push operation:
temp = rev * 10 + pop;
rev = temp;
但是,这种方法很危险,因为当
t
e
m
p
=
r
e
v
∗
10
+
p
o
p
temp = rev * 10 + pop
temp=rev∗10+pop时会导致溢出。
解释如下:
1.如果
t
e
m
p
=
r
e
v
∗
10
+
p
o
p
temp = rev * 10 + pop
temp=rev∗10+pop导致溢出,那么一定有
r
e
v
≥
I
N
T
M
A
X
10
rev\geq\frac{INTMAX}{10}
rev≥10INTMAX
2.如果
r
e
v
≥
I
N
T
M
A
X
10
rev\geq\frac{INTMAX}{10}
rev≥10INTMAX,那么
t
e
m
p
=
r
e
v
∗
10
+
p
o
p
temp = rev * 10 + pop
temp=rev∗10+pop一定会溢出
3.如果
r
e
v
=
=
I
N
T
M
A
X
10
rev==\frac{INTMAX}{10}
rev==10INTMAX,那么只要
p
o
p
>
7
,
t
e
m
p
=
r
e
v
∗
10
+
p
o
p
pop>7,temp = rev * 10 + pop
pop>7,temp=rev∗10+pop就会溢出
class Solution {
public:
int reverse(int x) {
int rev = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
if (rev > INT_MAX/10 || (rev == INT_MAX / 10 && pop > 7)) return 0;
if (rev < INT_MIN/10 || (rev == INT_MIN / 10 && pop < -8)) return 0;
rev = rev * 10 + pop;
}
return rev;
}
};
作者:LeetCode
整数字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
class Solution { //hash表
public:
int firstUniqChar(string s) {
map<char, int> mp;
for (auto x : s)
{
if (!mp.count(x))
{
mp[x] = 1;
}
else
{
mp[x]++;
}
}
for (int i = 0; i < s.size(); i++)
{
if (mp[s[i]] == 1)
{
return i;
}
}
return -1;
}
};
有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
class Solution { //hash
public:
bool isAnagram(string s, string t) {
map<char, int>mp[2];
for (auto x : s)
{
if (!mp[0].count(x))
{
mp[0][x] = 1;
}
else
{
mp[0][x]++;
}
}
for (auto y : t)
{
if (!mp[1].count(y))
{
mp[1][y] = 1;
}
else
{
mp[1][y]++;
}
}
if (mp[0] == mp[1])
{
return true;
}
else
{
return false;
}
}
};
方法二:
1.我们可以计算两个字符串中每个字母的出现次数并进行比较。因为
S
S
S和
T
T
T都只包含
A
−
Z
A-Z
A−Z的字母,所以一个简单的26位计数表就足够了。
2.我们需要两个计数器数表进行比较吗?实际上不是,因为我们可以用一个计数器表计算
s
s
s 字母的频率,用
t
t
t 减少计数器表中的每个字母的计数器,然后检查计数器是否回到零
class Solution { //hash
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size())
{
return false;
}
int counter[26] = {0};
for (int i = 0; i < s.size(); i++)
{
counter[s[i] - 'a']++; //这里使用数组,key值将char->int,来进行索引
counter[t[i] - 'a']--;
}
for (int cnt : counter)
{
if (cnt != 0)
{
return false;
}
}
return true;
};
验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
class Solution { //去除多余符号与空格,逆序后比较,耗时较长,不推荐
public:
bool isPalindrome(string s) {
for (auto &x : s)
{
x=tolower(x);
if (ispunct(x))
x = ' ';
}
//去除空格
int index = 0;
while ((index=s.find(' ',index)) != string::npos)
{
s.erase(index,1);
}
string t;
t.append(s.rbegin(), s.rend());
if (s == t)
{
return true;
}
else
{
return false;
}
}
};
字符串转换整数 (atoi)
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
说明:
假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [ − 2 31 , 2 31 − 1 ] [-2^{31},2^{31}-1] [−231,231−1]。如果数值超过这个范围,请返回 INT_MAX ( 2 31 − 1 ) (2^{31}-1) (231−1)或 INT_MIN ( − 2 31 ) (−2^{31}) (−231) 。
class Solution { //直接使用sstream来做,确实巧妙,想我还用了半天正则表达式。。。。。。 但是这个方法能自动考虑解决溢出问题,确实意想不到
public:
int myAtoi(string str) {
int digit=0;
istringstream is(str);
is>>digit;
return digit;
}
};
作者:san-gun
链接:https://leetcode-cn.com/problems/string-to-integer-atoi/solution/shi-yong-cbiao-zhun-zi-fu-chuan-ioku-sstreamzhong-/
报数
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。
-
1
-
11
-
21
-
1211
-
111221
class Solution { //hash和迭代器
public:
string countAndSay(int n) {
string str[32];
str[1] = "1";
map<char, int> mp;
for (int i = 1; i <= n; i++)
{
for (auto beg = str[i].begin(), end = str[i].end(); beg != end; beg++)
{
if (!mp.count(*beg))
{
mp[*beg] = 1;
}
else
{
mp[*beg]++;
}
if (beg + 1 == end)
{
str[i + 1].append(to_string(mp[*beg]));
str[i + 1].push_back(*beg);
mp.clear();
}
else if (*beg != *(beg + 1))
{
str[i + 1].append(to_string(mp[*beg]));
str[i + 1].push_back(*beg);
mp.clear();
}
}
}
return str[n];
}
};
链表问题
删除链表中的节点
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
从链表里删除一个节点 node 的最常见方法是修改之前节点的 next 指针,使其指向之后的节点。
因为,我们无法访问我们想要删除的节点 之前 的节点,我们始终不能修改该节点的 next 指针。相反,我们必须将想要删除的节点的值替换为它后面节点中的值,然后删除它之后的节点。
class Solution { //打破思维定势,需要一个前节点
public:
void deleteNode(ListNode* node) {
node->val = node->next->val;
node->next = node->next->next;
}
};
删除链表的倒数第N个节点
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
class Solution { //一次遍历,使用hash保存节点
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
map<int, ListNode* > mp;
int cnt = 1;
ListNode *p=head;
while (p != NULL)
{
mp[cnt++] = p;
p = p->next;
}
int cur = cnt - n;
if (mp[cur]->next == NULL && cnt==2) //1个元素的情况
{
return NULL;
}
else if(mp[cur]->next == NULL && cnt > 2)
{
mp[cur - 1]->next = NULL;
}
else if (mp[cur]->next != NULL && n == cnt - 1) //删除首元素的情况
{
return mp[cur + 1];
}
else
{
mp[cur - 1]->next = mp[cur]->next;
}
return head;
}
};
方法1-两次遍历
我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 ( L − n + 1 ) (L - n + 1) (L−n+1) 个结点,其中 L L L是列表的长度。只要我们找到列表的长度 L L L,这个问题就很容易解决。
算法
首先我们将添加一个哑结点作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度
L
L
L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第
(
L
−
n
)
(L - n)
(L−n)个结点那里。我们把第
(
L
−
n
)
(L - n)
(L−n)个结点的 next 指针重新链接至第
(
L
−
n
+
2
)
(L - n + 2)
(L−n+2)个结点,完成这个算法。
方法2-一次遍历
上述算法可以优化为只使用一次遍历。我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动
n
+
1
n+1
n+1 步,而第二个指针将从列表的开头出发。现在,这两个指针被
n
n
n 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第
n
n
n 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。
下面图示n=2时的情况
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* p = dummyHead;
ListNode* q = dummyHead;
for( int i = 0 ; i < n + 1 ; i ++ ){
q = q->next;
}
while(q){
p = p->next;
q = q->next;
}
ListNode* delNode = p->next;
p->next = delNode->next;
delete delNode;
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
作者:MisterBooo
链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/solution/dong-hua-tu-jie-leetcode-di-19-hao-wen-ti-shan-chu/
反转链表
反转链表
class Solution { //略取巧,只反转值,未对指针本身进行操作
public:
ListNode* reverseList(ListNode* head) {
stack<int> stack;
ListNode* p=head;
ListNode* head_temp = p;
while (p != NULL)
{
stack.push(p->val);
p = p->next;
}
p = head_temp;
while (p != NULL)
{
p->val = stack.top();
stack.pop();
p = p->next;
}
return head_temp;
}
};
官方解法.迭代
假设存在链表 1 → 2 → 3 → Ø,我们想要把它改成 Ø ← 1 ← 2 ← 3。
在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。不要忘记在最后返回新的头引用!
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *prev = nullptr;
ListNode *curr = head;
while (curr != nullptr)
{
ListNode *nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
};
合并两个有序链表
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* Prev = new ListNode(0);
ListNode* TempHead = Prev;
while (l1 != nullptr || l2 != nullptr)
{
if (l1 == nullptr)
{
Prev->next = l2;
l2 = l2->next;
Prev = Prev->next;
continue;
}
else if (l2 == nullptr)
{
Prev->next = l1;
l1 = l1->next;
Prev = Prev->next;
continue;
}
if ((l1->val) <= (l2->val))
{
Prev->next = l1;
l1 = l1->next;
Prev = Prev->next;
}
else
{
Prev->next = l2;
l2 = l2->next;
Prev = Prev->next;
}
}
return TempHead->next;
}
};
改进:
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表。
class Solution {
public ListNode mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* Prev = new ListNode(-1);
ListNode* TempHead = Prev;
while (l1 != nullptr && l2 != nullptr)
{
if (l1->val <= l2->val)
{
Prev->next = l1;
l1 = l1->next;
}
else
{
Prev->next = l2;
l2 = l2->next;
}
Prev = Prev->next;
}
if (l1==nullptr)
{
Prev->next = l2;
}
else
{
Prev->next = l1;
}
return TempHead->next;
}
};
回文链表
判断一个链表是否为回文链表。
能否用 O(n) 时间复杂度和 O(1) 空间复杂度
class Solution { //使用额外空间vector来判断回文
public:
bool isPalindrome(ListNode* head) {
ListNode* temp = head;
vector<int> vec;
while (temp != NULL)
{
vec.push_back(temp->val);
temp = temp->next;
}
for (auto x : vec)
{
cout << x << " ";
}
vector<int> vec_t = vec;
std::reverse(vec.begin(), vec.end());
if (vec_t ==vec )
{
return true;
}
else
{
return false;
}
}
};
解法2:
1.快慢指针找到链表的中点
2.翻转链表前半部分
3.回文校验
class Solution {
public:
bool isPalindrome(ListNode* head) {
//没有节点或仅有1个节点的情况
if (head == NULL || head->next == NULL)
{
return true;
}
ListNode* slow = head;
ListNode* fast = head->next;
//先遍历,用快慢指针找到中点
while (fast != NULL && fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
}
//对中点前的链表进行反转
ListNode* pre = NULL;
ListNode* cur = head;
slow = slow->next; //**slow前进一位(slow之前的节点反转),确保能反转中点以前的所有节点
while (cur != slow) //
{
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
//判断两序列是否相同
if (fast == NULL) //如果是奇数个数的链表则中心元素前进一位
{
//slow = slow->next;
pre = pre->next;
}
while (pre != NULL)
{
if (slow->val != pre->val)
{
return false;
}
slow = slow->next;
pre = pre->next;
}
return true;
}
};
环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
class Solution { //哈希表判断
public:
bool hasCycle(ListNode *head) {
ListNode* cur = head;
set<ListNode*> set;
while (cur != NULL)
{
if (!set.count(cur))
{
set.insert(cur);
}
else
{
return true;
}
cur = cur->next;
}
return false;
}
};
方法2:
通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。
其他情况又会怎样呢?例如,我们没有考虑快跑者在慢跑者之后两步或三步的情况。但其实不难想到,因为在下一次或者下下次迭代后,又会变成上面提到的情况 A。
class Solution { //快慢指针判断
public:
bool hasCycle(ListNode *head) {
if(head==NULL || head->next==NULL)
{
return false;
}
ListNode* slow=head;
ListNode* fast=head->next;
while(slow!=fast)
{
if(fast==NULL || fast->next==NULL)
{
return false;
}
slow=slow->next;
fast=fast->next->next;
}
return true;
}
};
树问题
二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
class Solution { //递归
public:
int maxDepth(TreeNode* root) {
if (root == NULL)
{
return 0;
}
else
{
return max(maxDepth(root->left), maxDepth(root->right))+1;
}
}
};
迭代解题思路:
从包含根结点且相应深度为 1 的栈开始。然后我们继续迭代:将当前结点弹出栈并推入子结点。每一步都会更新深度
class Solution { //迭代
public:
int maxDepth(TreeNode* root) {
stack<pair<TreeNode*, int>> stack;
if (root != NULL)
{
stack.push(make_pair(root, 1));
}
int depth = 0; //记录最大深度
while (!stack.empty())
{
pair<TreeNode*, int> current = stack.top(); //弹出当前节点
stack.pop();
root = current.first;
int current_depth = current.second;
if (root != NULL) //若当前节点不为空,则推入子节点,并更新深度
{
depth = max(depth, current_depth);
stack.push(make_pair(root->left, current_depth + 1));
stack.push(make_pair(root->right, current_depth + 1));
}
}
return depth;
}
};
验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
class Solution { //递归
public:
bool isValidBST(TreeNode* root) {
return helper(root, NULL, NULL);
}
bool helper(TreeNode* node, int* lower, int* upper) //使用指针来传递上下边界值
{
if (node == NULL) return true; //基线条件
int val = node->val;
if (upper != NULL && val >= *upper) return false; //左子树存在最大值,每个节点都比这个小
if (lower != NULL && val <= *lower) return false; //右子树存在最小值,每个节点都比这个大
//递归条件
if (!helper(node->left, lower, &val)) return false; //左子树存在大于上界值的数,就返回false
if (!helper(node->right, &val, upper)) return false; //右子树存在小于于下界值的数,就返回false
return true;
}
};
因为二分搜索树在中序便利下,是一个递增的数列,所以可以使用这个性质来进行判断
class Solution { //使用中序遍历判断
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> stack;
long min = LONG_MIN; //leetcode的测试用例存在边界最小值,INT_MIN不够小,换为LONG_MIN
while (!stack.empty() || root != NULL)
{
while(root!=NULL)
{
stack.push(root);
root = root->left;
}
auto top = stack.top();
stack.pop();
if (top->val > min)
{
min = top->val;
}
else
{
return false;
}
root=top->right;
}
return true;
}
};
对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
思路:
如果一个树的左子树与右子树镜像对称,那么这个树是对称的。
如果同时满足下面的条件,两个树互为镜像:
- 它们的两个根结点具有相同的值。
- 每个树的右子树都与另一个树的左子树镜像对称。
class Solution { //递归
public:
bool isSymmetric(TreeNode* root) {
return isMirror(root,root);
}
bool isMirror(TreeNode* t1, TreeNode* t2) { //左右子树
//基线条件
if (t1 == NULL && t2 == NULL) return true;
if (t1 == NULL || t2 == NULL) return false;
//递归条件
return (t1->val == t2->val) && isMirror(t1->right, t2->left) && isMirror(t1->left, t2->right);
}
};
二叉树的层次遍历
给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
class Solution { //迭代法
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> queue; //队列实现层次遍历
vector<int> resT;
vector<vector<int>> res;
if (root == NULL)
return res;
queue.push(root);
while (!queue.empty())
{
auto size = queue.size();
while (size != 0) //使用size控制每一层的遍历,size为0即一层遍历结束
{
auto node = queue.front();
resT.push_back(node->val);
if (node->left != NULL)
{
queue.push(node->left);
}
if (node->right != NULL)
{
queue.push(node->right);
}
queue.pop();
size--;
}
res.push_back(resT);
resT.clear();
}
return res;
}
};
将有序数组转换为二叉搜索树
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
class Solution { //递归
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
return helper(nums,0, nums.size()-1);
}
TreeNode* helper(vector<int>& nums,int left,int right)
{
//基线条件
if (left > right)
return NULL;
//递归条件
int mid = (left + right) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = helper(nums, left,mid - 1); //**递归的方式
root->right = helper(nums, mid+1, right);
return root;
}
};
排序和搜索问题
合并两个有序数组
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
class Solution { //使用库函数
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> res(m + n);
std::merge(nums1.begin(), nums1.begin() + m, nums2.begin(), nums2.begin() + n, res.begin());
nums1 = res;
}
};
从后向前数组遍历
因为 nums1 的空间都集中在后面,所以从后向前处理排序的数据会更好,节省空间,一边遍历一边将值填充进去
设置指针 len1 和 len2 分别指向 nums1 和 nums2 的有数字尾部,从尾部值开始比较遍历,同时设置指针 len 指向 nums1 的最末尾,每次遍历比较值大小之后,则进行填充
当 len1<0 时遍历结束,此时 nums2 中海油数据未拷贝完全,将其直接拷贝到 nums1 的前面,最后得到结果数组
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int len1 = m - 1;
int len2 = n - 1;
int len = m + n - 1;
while (len1 >= 0 && len2 >= 0)
{
nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
}
while (len2 >= 0)
nums1[len--] = nums2[len2--];
}
};
第一个错误的版本
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
算法:类似二分搜索
class Solution {
public:
int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left<right)
{
int mid = left + (right - left)/2; //*****若使用mid=(left+right)/2会导致溢出
if (isBadVersion(mid) == true)
{
right = mid; //考虑[0,0,0,1,1,1,1]的情况
}
else
{
left = mid+1; //考虑[0,0,0,0,1,1,1]的情况
}
}
return left;
}
};
动态规划问题
爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
,这个问题可以被分解为一些包含最优子结构的子问题,即它的最优解可以从其子问题的最优解来有效地构建,我们可以使用动态规划来解决这一问题。
第 i i i 阶可以由以下两种方法得到:
1.在第 ( i − 1 ) (i-1) (i−1)阶后向上爬一阶。
2.在第 ( i − 2 ) (i-2) (i−2)阶后向上爬 22 阶。
所以到达第 i i i阶的方法总数就是到第 ( i − 1 ) (i-1) (i−1)阶和第 ( i − 2 ) (i-2) (i−2)阶的方法数之和。
class Solution {
public:
int climbStairs(int n) {
if (n == 1)
return 1;
vector<int> dp(n + 1);
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
}
};
买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<int> prev=initPrev(prices);
vector<int> opt(prices.size());
if (prices.size() == 0)
return 0;
opt[0] = 0;
for (int i = 1; i < prices.size(); i++)
{
opt[i] = max(prices[i] - prev[i], opt[i - 1]);
}
return opt[prices.size() - 1];
}
vector<int> initPrev(vector<int>& prices)
{
vector<int> prev(prices.size());
int minVal = INT_MAX;
for (int i = 0; i < prices.size(); i++)
{
minVal = min(minVal, prices[i]);
prev[i] = minVal;
}
return prev;
}
};
最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if (nums.size() == 0)
return 0;
vector<int> dp(nums.size());
dp[0] = nums[0];
for (int i = 1; i < nums.size(); i++)
dp[i] = max(nums[i], dp[i - 1] + nums[i]); //
int res = dp[0];
for (int i = 1; i < nums.size(); i++)
res = max(res, dp[i]);
return res;
}
};
打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
vector<int> opt(nums.size());
opt[0] = nums[0];
opt[1] = max(nums[0], nums[1]);
for (int i = 2; i < nums.size(); i++)
opt[i] = max(nums[i] + opt[i - 2], opt[i - 1]);
return opt[nums.size()-1];
}
};
设计问题
Shuffle an Array
打乱一个没有重复元素的数组。
这里简单解释一下Fisher-Yates 洗牌算法
Fisher–Yates shuffle是对有限序列生成一个随机排列的算法,所有的排列是等概率的,该算法是无偏的、高效的,算法的时间正比于乱序的数组。
各列含义:范围、当前数组随机交换的位置、剩余没有被选择的数、已经随机排列的数
Range | Roll | Scratch | Result |
---|---|---|---|
1 2 3 4 5 6 7 8 |
第一轮:从1到8中随机选择一个数,得到6,则交换当前数组中第8和第6个数
Range | Roll | Scratch | Result |
---|---|---|---|
1-8 | 6 | 1 2 3 4 5 8 7 | 6 |
第二论:从1到7中随机选择一个数,得到2,则交换当前数组中第7和第2个数
Range | Roll | Scratch | Result |
---|---|---|---|
1–7 | 2 | 1 7 3 4 5 8 | 2 6 |
下一个随机数从1到6中摇出,刚好是6,这意味着只需把当前线性表中的第6个数留在原位置,接着进行下一步;以此类推,直到整个排列完成。
Range | Roll | Scratch | Result |
---|---|---|---|
1–6 | 6 | 1 7 3 4 5 | 8 2 6 |
1–5 | 1 | 5 7 3 4 | 1 8 2 6 |
1–4 | 3 | 5 7 4 | 3 1 8 2 6 |
1–3 | 3 | 5 7 | 4 3 1 8 2 6 |
1-2 | 1 | 7 | 5 4 3 1 8 2 6 |
截至目前,所有需要的置乱已经完成,所以最终的结果是:7 5 4 3 1 8 2 6
class Solution {
public:
Solution(vector<int>& nums)
:num(nums),copy(nums)
{}
/** Resets the array to its original configuration and return it. */
vector<int> reset()
{
return copy;
}
/** Returns a random shuffling of the array. */
vector<int> shuffle() //使用Fisher-Yates 洗牌算法
{
static default_random_engine e; //static 让随机数引擎生成的序列不是伪随机的
for (int i = num.size()-1; i > 0; i--)
{
int x = e() % num.size();
swap(num[x], num[i]);
}
return num;
}
private:
vector<int> num;
vector<int> copy;
};
最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。
class MinStack {
public:
/** initialize your data structure here. */
MinStack()=default;
void push(int x) {
stack.push_back(x);
}
void pop() {
stack.pop_back();
}
int top() {
return stack.back();
}
int getMin() {
return (*min_element(stack.begin(), stack.end()));
}
private:
vector<int> stack;
};
数学问题
Fizz Buzz
写一个程序,输出从 1 到 n 数字的字符串表示。
-
如果 n 是3的倍数,输出“Fizz”;
-
如果 n 是5的倍数,输出“Buzz”;
-
如果 n 同时是3和5的倍数,输出 “FizzBuzz”。
class Solution {
public:
vector<string> fizzBuzz(int n) {
vector<string> res(n+1);
for (int i = 0; i < n+1; i++)
{
if (i % 3 == 0 && i%5==0)
{
res[i] = "FizzBuzz";
}
else if (i % 3 == 0)
{
res[i] = "Fizz";
}
else if (i % 5 == 0)
{
res[i] = "Buzz";
}
else
{
res[i] = to_string(i);
}
}
res.erase(res.begin());
return res;
}
};
计数质数
统计所有小于非负整数 n 的质数的数量。
class Solution {
public:
int countPrimes(int n) {
vector<int> res(n+1,1);
if (n <= 2)
return 0;
res[0] = res[1] = -1;
for (int i = 2; i <=sqrt(n); i++)
{
int j = i;
while (i * j < n+1)
{
res[i * j] = 0;
j = j + 1;
}
}
return count(res.begin(), res.end()-1, 1);
}
};
但依旧可以优化(厄拉多塞筛法)
我们在进行顺序遍历时,每取得一个数(排除0、1),如果将它所有的倍数(排除0、1、本身)都清除
int countPrimes(int n) {
int count = 0;
//初始默认所有数为质数
vector<bool> signs(n, true);
for (int i = 2; i < n; i++) {
if (signs[i]) {
count++;
for (int j = i + i; j < n; j += i) {
//排除不是质数的数
signs[j] = false;
}
}
}
return count;
}
/*作者:magicalchao
链接:https://leetcode-cn.com/problems/count-primes/solution/ji-shu-zhi-shu-bao-li-fa-ji-you-hua-shai-fa-ji-you/
来源:力扣(LeetCode)*/
class Solution {
public:
int countPrimes(int n) {
vector<int> res(n+1,1);
if (n <= 2)
return 0;
res[0] = res[1] = -1;
for (int i = 2; i <=sqrt(n); i++)
{
if (res[i])
{
for (int j = 2; j * i <= n; j++)
{
res[i * j] = 0;
}
}
}
return count(res.begin(), res.end()-1, 1);
}
};
3的幂
给定一个整数,写一个函数来判断它是否是 3 的幂次方。(不使用循环或者递归来完成本题)
题解:
class Solution {
public:
bool isPowerOfThree(int n) {
if (n > 0 && 1162261467 % n == 0)
return true;
else
return false;
}
};
罗马数字转整数
罗马数字包含以下七种字符: 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 到 3999 的范围内。
class Solution {
public:
int romanToInt(string s) {
map<char, int> mp = { {'I',1},{'V',5},{'X',10},{'L',50},{'C',100},{'D',500},{'M',1000} };
int res = 0;
for (int i = 0; i < s.size() - 1; i++)
{
if ((s[i] == 'I' && s[i + 1] == 'V') || (s[i] == 'I' && s[i + 1] == 'X'))
{
res = res - 1;
}
else if ((s[i] == 'X' && s[i + 1] == 'L') || (s[i] == 'X' && s[i + 1] == 'C'))
{
res = res - 10;
}
else if ((s[i] == 'C' && s[i + 1] == 'D') || (s[i] == 'C' && s[i + 1] == 'M'))
{
res = res - 100;
}
else
{
res = res + mp[s[i]];
}
}
res = res + mp[s.back()];
return res;
}
};
位1的个数
编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
class Solution {
public:
int hammingWeight(uint32_t n) {
bitset<32> b(n);
return b.count();
}
};
方法二:直接循环遍历
public int hammingWeight(int n) {
int bits = 0;
int mask = 1;
for (int i = 0; i < 32; i++) {
if ((n & mask) != 0) {
bits++;
}
mask <<= 1;
}
return bits;
}
汉明距离
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。
class Solution {
public:
int hammingDistance(int x, int y) {
bitset<32> a(x);
bitset<32> b(y);
int res = 0;
for (int i = 0; i < 32; i++)
{
if ((a[i] ^ b[i]) == 1)
{
res++;
}
}
return res;
}
};
颠倒二进制位
颠倒给定的 32 位无符号整数的二进制位。
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
bitset<32> bit(n);
bitset<32> bitT;
int j = 31;
for (int i = 0; i < 32; i++)
{
bitT[j] = bit[i];
j--;
}
uint32_t res=bitT.to_ulong();
return res;
}
};
有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
class Solution {
public:
bool isValid(string s) {
map<char, char > mp= { {'(', ')'}, { '{','}' }, { '[',']' }};
stack<char> stack;
if (s.empty())
return true;
while (!s.empty())
{
stack.push(s.front());
s.erase(s.begin());
if (s.empty())
break;
while (!stack.empty() && mp[stack.top()] == s.front() )
{
stack.pop();
s.erase(s.begin());
if (s.empty())
break;
}
}
if (!stack.empty())
return false;
return true;
}
};
算法虽然使用了栈作为工具,但仍可以优化
1.初始化栈 S。
2.依次处理表达式的每个括号。
3.如果遇到开括号,我们只需将其推到栈上即可。这意味着我们将稍后处理它,让我们简单地转到前面的 子表达式。
4.如果我们遇到一个闭括号,那么我们检查栈顶的元素。如果栈顶的元素是一个 相同类型的 左括号,那么我们将它从栈中弹出并继续处理。否则,这意味着表达式无效。
5.如果到最后我们剩下的栈中仍然有元素,那么这意味着表达式无效。
class Solution {
public:
bool isValid(string s) {
map<char, char > mp= { {')', '('}, { '}','{' }, { ']','[' }}; //hash表反向列入
stack<char> stack;
for (int i = 0; i < s.size(); i++)
{
char c = s[i];
if (mp.count(c)) //如果遇到右括号
{
char top = stack.empty() ? '#' : stack.top(); //获取堆栈的顶部元素。
stack.pop();
if (top != mp[c]) //如果此括号的映射与堆栈的top元素不匹配,则返回false。
{
return false;
}
}
else //左括号则压入栈
{
stack.push(c);
}
}
return stack.empty();
}
};
缺失数字
给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。
class Solution {
public:
int missingNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
int max = nums[nums.size() - 1];
int min = 0;
int sum1 = (max + min) * (max + 1) / 2;
int res = sum1 - accumulate(nums.begin(), nums.end(), 0);
if (res == 0 && nums[0]!=0)
return 0;
else if(res==0 && nums[0]==0)
return max+1;
else
return res;
}
};
改进
class Solution {
public:
int missingNumber(vector<int>& nums) {
int Exsum =(nums.size()*(nums.size()+1))/2;
int sum=accumulate(nums.begin(), nums.end(), 0);
return Exsum-sum;
}
};