解题思路:让两个指针分别指向两个链表,当遍历到最末尾的结点后,指针指向另一个链表的头结点。例如:A 和 B 开始的时候分别指向 4 和 5。两个指针分别遍历链表,当 A 遍历到末尾结点的 5 时,下一步让其指向头结点为 5 的结点。而 B,让其指向头结点为 4 的结点。这样,A 和 B 指针在指向公共结点的时候都走了相同的距离,所以它们相遇的时候该结点就是公共结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *node1 = headA;
ListNode *node2 = headB;
while (node1 != node2) {
if(node1 != NULL)
node1 = node1->next;
else
node1 = headB;
if(node2 != NULL)
node2 = node2->next;
else
node2 = headA;
}
return node1;
}
};
解题思路:由题可知,数组是排好序的,所以可以先用二分查找找出 target 所在的下标,然后根据下标向前向后查找即可。
class Solution {
public:
int search(vector<int>& nums, int target) {
int count = 0, mid, low = 0, high = nums.size()-1;
while(low <= high)
{
mid = low + (high - low)/2;
if(nums[mid] == target){
count = searchcount(nums, target, mid);
break;
}
else if(nums[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
return count;
}
int searchcount(vector<int>& nums, int target, int index)
{
int count = 0, left = index - 1, right = index + 1;
while( left >= 0 ) {
if(nums[left--] == target)
count++;
else
break;
}
while( right < nums.size() ) {
if(nums[right++] == target)
count++;
else
break;
}
return count + 1;
}
};
解题思路:是有序数组,第一个想到的就是二分查找。
class Solution {
public:
int missingNumber(vector<int>& nums) {
int left = 0, right = nums.size();
while(left < right){
int mid = left + (right - left)/2;
if(mid == nums[mid]){
left = mid+1;
}else{
right = mid;
}
}
return left;
}
};
解题思路:二叉搜索树中序遍历可以得到递增有序序列。将二叉树中序非递归遍历反向操作,每次先右子树入栈即可获得递减有序序列。这样就能很快找到第 k 大的结点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int kthLargest(TreeNode* root, int k) {
stack<TreeNode*> sta;
TreeNode* p = root;
int count = 0, result;
while(!sta.empty() || p != NULL)
{
while(p)
{
sta.push(p);
p = p->right;
}
p = sta.top();
if(++count == k)
result = p->val;
sta.pop();
p = p->left;
}
return result;
}
};
解题思路:利用递归计算二叉树的深度。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL)
return 0;
int i = maxDepth(root->left);
int j = maxDepth(root->right);
return i > j ? i+1: j+1;
}
};
解题思路:利用递归计算二叉树的深度。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
bool isBalanced(TreeNode* root) {
return !root ? true : abs(depth(root->left) - depth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
int depth(TreeNode* cur) { //计算二叉树的最大深度
return !cur ? 0 : max(depth(cur->left), depth(cur->right)) + 1;
}
};
解题思路:(1) 用哈希表。(2) 题目中强调其他数字出现两次,如果只有一个数字的情况下,我们可是使用异或(相同为0,不同为1)得出要找的数字。但是题中要找的数数字有两个,所以这个时候还是需要对整个序列求异或。这样最终得到的是要找的两个数字的异或的结果。此时,我们可以根据得到的值,将原本序列分成两个序列(每个序列除了一个单独数字外,其余的数字都是成对出现的),然后再对这两个序列进行异或操作就能找出两个只出现一次的数字。
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
unordered_map<int, int> res;
vector<int> number;
for (int i = 0; i < nums.size(); i++) {
res[nums[i]]++;
}
for (auto c : nums) {
if (res[c] == 1) {
number.emplace_back(c);
}
}
return number;
}
};
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
//总共分为两个步骤:(1) 整个序列求异或 (2) 根据异或值划分序列后再求异或
//第一步:求所有的数异或的结果
int k = 0;
for (int num : nums) {
k ^= num;
}
//获得k中最低位的1,k里面两数不同,故肯定有一位1是不同的,可以以此区分两个序列
int mask = 1;
while ((k & mask) == 0) {
mask <<= 1;
}
int a = 0;
int b = 0;
//第二步:根据 mask 划分序列后再求异或
for (int num : nums) {
if ((num & mask) == 0) {
a ^= num; // 找到那位不为1的元素, 其余数为偶数,会自动消去
} else {
b ^= num; // 找到那位为1的元素
}
}
return {a, b};
}
};
解题思路:(1) 用哈希表。(2) 使用位运算。得注意的是:如果某个数字出现 3 次,那么这个 3 个数字的和肯定能被 3 整除,则其对应二进制位的每一位的和也能被 3 整除。统计数组中每个数字的二进制中每一位的和,判断该和是否能被 3 整除。若可以,则只出现一次的数字的二进制数中那一位为 0,否则为 1。
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int, int> mp;
for(int n : nums) mp[n] ++;
int ans;
for(auto pr : mp){
if(pr.second == 1){
ans = pr.first;
break;
}
}
return ans;
}
};
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ans = 0;
for(int i = 0; i < 32; ++i){
int cnt = 0;
for(int n : nums){
// n & 1 << i 的值大于0即为真
if(n & (1 << i)) cnt++;
}
// 构造只出现一次的那个数字,采用异或的方法生成二进制中的每一位
if(cnt % 3 == 1) ans ^= (1 << i);
}
return ans;
}
};
解题思路:题中所给的序列是递增排序序列,所以可以使用双指针 low 和 high 解决该问题。若 low 和 high 的两个元素相加为 target 此时记录相应的值返回即可。若两者的和大于 target,令 high--。反之令 low++。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int size = nums.size();
vector<int> asd(2, 0);
int low = 0, high = size-1;
while(low < high)
{
if(nums[low] + nums[high] == target){
asd[0] = nums[low];
asd[1] = nums[high];
}
if(nums[low] + nums[high] > target){
high--;
}
else{
low++;
}
}
return asd;
}
};
解题思路:该题所求的是所有和为 target 的连续正整数序列的组合。这里可以使用暴力枚举解决该题。首先需要确定边界,limit 为 target / 2 向下取整。内层循环完成累加操作,若累加和等于 target,则记录相应的数字序列。循环终止的条件是累加和大于 target。
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> vec;
vector<int> res;
int sum = 0, limit = (target - 1) / 2; //(target - 1)/2 等效于 target / 2 下取整
for (int i = 1; i <= limit; ++i) {
for (int j = i;; ++j) {
sum += j; //完成累加操作
if (sum > target) {
sum = 0;
break;
}
else if (sum == target) { //记录数字序列
res.clear();
for (int k = i; k <= j; ++k) res.emplace_back(k);
vec.emplace_back(res);
sum = 0;
break;
}
}
}
return vec;
}
};
下面这个方法是对上述代码的改进。每次操作之后的序列和操作之前的序列相比大部分数字都是一样的,只是增加或减少了一个数字,因此我们可以在前一个序列的和的基础上求操作之后的序列的和。这样可以减少很多不必要的运算,从而提高代码效率。
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
vector<vector<int>> result;
vector<int> asd;
if(target < 3)
return result;
int low = 1, high = 2;
int middle = (target+1)/2;
int sum = low + high;
while(low < middle)
{
if(sum == target)
{
Add(result, asd, low, high);
}
while(sum > target && low < middle)
{
sum -= low;
low++;
if(sum == target)
{
Add(result, asd, low, high);
}
}
high++;
sum += high;
}
return result;
}
void Add(vector<vector<int>>& result, vector<int>& asd, int low, int high)
{
for(int i = low; i <= high ; i++)
asd.push_back(i);
result.push_back(asd);
asd.clear();
}
};
若上面的题目不要求组合内的序列是连续的,那该怎么解决?
#include <iostream>
#include <list>
#include <cmath>
using namespace std;
int *a, *b, N, n;
int total = 0;
void find(int sum, int n, int b[])
{
//递归出口
if(sum <= 0 || n <= 0)
return;
//输出找到的结果
if(sum == b[n-1]) //表示找到了一个值
{
a[n-1] = 1;
for(int i = 0; i < N; i++)
{
if(a[i] == 1)
cout << b[i] <<" ";
}
cout <<endl;
total++;
}
a[n-1] = 1;
find(sum-b[n-1], n-1, b); //如果放入n,则从剩余n-1个数中填满sum-n
a[n-1] = 0;
find(sum, n-1, b); //如果不放入n,从n-1个数中填满sum
}
int main()
{
int sum;
cout<<"Please Input n&sum:"<<endl;
cin>>N>>sum;
a = new int[N];
b = new int[N];
for(int i = 0; i < N; i++)
cin >> b[i];
n = N;
find(sum, n, b);
cout<<"Total:"<<total<<endl;
//cout << Number <<endl;
delete []a;
delete []b;
return 0;
}
解题思路:(1) 该题是利用空格进行划分,所以可以使用双指针从后往前将字符串进行分割。 (2) 利用 strtok 函数对字符串进行分割,利用 vector 存储分割后的单词,接着完成一个 reverse 操作。最后根据题意,添加相应的空格即可。
class Solution {
public:
string reverseWords(string s) {
string str;
if(s.empty()) //输入字符为空,返回空
return str;
cout << s.size() <<endl;
int i{0}, j{0}; //i,j用来表示单词开始和结束的位置
j = s.size() - 1;
for(j; j >= 0; --j)
{
if(s[j] != ' ') //遇到不是空格的
{
i = j;
while(i >=0 && s[i] != ' ') //从j开始向左寻找单词,i>=0防止越界访问
--i;
for(int k = i + 1; k <= j; ++k) //单词序列加入字符串中
str.push_back(s[k]);
str.push_back(' '); //加入一个空格
j = i; //改变j的位置
}
}
if(str.size() > 0)
str.pop_back(); //移除末尾空格
return str;
}
};
class Solution {
public:
string reverseWords(string s) {
vector<string> asd;
int size = s.size();
char* p = new char[size+1];
strcpy(p, s.c_str());
char* token = strtok(p, " ");
while(token != NULL){
asd.push_back(token);
token = strtok(NULL, " ");
}
reverse(asd.begin(), asd.end());
string result;
for(int i = 0; i < asd.size(); i++){
if(i != asd.size()-1)
result += asd[i] + " ";
else
result += asd[i];
}
delete []p;
return result;
}
};
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(), s.begin() + n);
reverse(s.begin() + n, s.end());
reverse(s.begin(), s.end());
return s;
}
};
解题思路:直接根据题意做题,用变量记录划窗中的最大值和其下标。当划窗移动的时候,先判断最值下标是否在区间内。如果在,只用对比新添加的元素和最值的大小即可。如果不在,就需要重新寻找划窗中的最大值了。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int>a;
if(nums.size() == 0){
return a;
}
int n = nums.size() - k + 1; // 滑动窗口滚动的次数
int t = -1; // 当前滑窗最大值的下标
int max; // 当前滑窗最大值
// 遍历,找每个滑窗的最大值
for(int i = 0;i < n ;i++){
// 上个滑窗的最大值还在当前滑窗内,如果新加入的元素比最大值还大,更新最大值和其下标
if(t >= i){
if(nums[i+k-1] >= max){
t = i+k-1;
max = nums[i+k-1];
}
}
// 不在的时候重新遍历当前滑窗,这里效率就低
else{
max = nums[i];
for(int j = i + 1;j < k + i;j++){
if(nums[j] >= max){
t = j;
max = nums[j];
}
}
}
// 添加最大值
a.push_back(max);
}
return a;
}
};
解题思路:解决该题需要用到两个数据结构,queue 和 deque,其中 queue 用于存储数据,deque 用于记录序列的最大值。
push_back():当添加一个新的数据的时候,如果 deque 不为空的情况下且其中的值小于添加的值,那么就将 deque 中的数据 pop_back(),然后将数据同时添加到 deque 和 queue 中。
pop_front():正常 pop 队列元素即可。需要添加的代码是,如果 queue 和 deque 队首元素相等,则两个都要 pop 元素。
max_value():deque 为空时 return -1 ,不为空时 return deque 的队首元素。
class MaxQueue {
queue<int> q;
deque<int> d;
public:
MaxQueue() {
}
int max_value() {
if (d.empty())
return -1;
return d.front();
}
void push_back(int value) {
while (!d.empty() && d.back() < value) {
d.pop_back();
}
d.push_back(value);
q.push(value);
}
int pop_front() {
if (q.empty())
return -1;
int ans = q.front();
if (ans == d.front()) {
d.pop_front();
}
q.pop();
return ans;
}
};
解题思路:有一串连续的数字(无重复),这串数字中最大值为 m,最小值为 n,这串数字一共包含 m-n+1 个数字。同样,如果我们能够知道 5 张扑克牌中的最大值 maxValue 和最小值 minValue,那我们就知道,要使它为顺子需要 maxValue - minValue + 1 张牌。
(1) 在查找 maxValue 和 minValue 过程中,跳过大小王。
(2) 如果 maxValue - minValue + 1 > 5,说明题目给的 5 张牌不足以构成顺子,即使里面有大小王,也不够用来填补使它构成顺子,所以返回 false。
(3) 如果 maxValue - minValue + 1 <= 5,说明 5 张牌足以构成顺子(里面的大小王能填补在合适位置),返回 true。
class Solution {
public:
bool isStraight(vector<int>& nums) {
bool m[15];
memset(m, 0, sizeof(m));
int minValue = 14, maxValue = 0;
for (int item : nums) {
if (item == 0) {
continue;
}
if (m[item]) {
return false;
}
m[item] = true;
minValue = min(minValue, item);
maxValue = max(maxValue, item);
}
return maxValue - minValue + 1 <= 5;
}
};
解题思路:这个问题实际上是约瑟夫问题。下面这个例子是N=8 m=3的例子。我们定义F(n,m)表示最后剩下那个人的索引号,因此我们只关系最后剩下来这个人的索引号的变化情况即可。
现在我们知道了G的索引号的变化过程,那么我们反推一下从N = 7 到N = 8 的过程。我们先把被删除的C补充回来,然后右移m个人,发现溢出了,再把溢出的补充在最前面,经过这个操作就恢复了N = 8 的排列了!
因此我们可以推出递推公式 f(8,3) = [ f(7, 3) + 3 ] % 8。进行推广泛化,即 f(n,m) = [ f(n−1,m) + m ]%n 。
法一:递推公式
class Solution {
public:
int lastRemaining(int n, int m) {
int pos = 0; // 最终留下的初始位置
for(int i = 2; i <= n; i++){
pos = (pos + m) % i; // 每次循环右移
}
return pos;
}
};
法二:用链表
int lastRemaining(int n, int m)
{ // n个数字,每次删除第m个数字
if(n < 1 || m < 1)
return -1;
int i = 0;
list<int> a;
for(i = 0; i < n; i++)
a.push_back(i);
list<int>::iterator current = a.begin();
list<int>::iterator next;
while(a.size() > 1){
for(int i = 1; i < m; i++)
{
current++;
if(current == a.end())
current = a.begin();
}
next = ++current;
if(next == a.end())
next = a.begin();
--current;
a.erase(current);
current = next;
}//end while
return *(current);
}
解题思路:最简单的办法是使用两层循环解决该问题,即外层循环从 0 开始,内层循环使用一个变量记录循环过程中最大的利润。这样算法复杂度就是 O(n^2)。其实在遍历过程中记录当前元素之前的最小值,那么就可以优化掉一次循环。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0)
return 0;
int minPrice = prices[0];
int maxGap = 0;
for(int i = 0; i < prices.size(); i++){
minPrice = min(minPrice, prices[i]); // 更新股票最小值
maxGap = max(maxGap, prices[i] - minPrice); // 更新利润最大值
}
return maxGap;
}
};
解题思路:根据题意做题即可。
class Solution {
public:
int strToInt(string str) {
int i = 0, flag = 1;
long res = 0; //默认flag = 1,正数
while (str[i] == ' ') i++;
if (str[i] == '-') flag = -1;
if (str[i] == '-' || str[i] == '+') i ++;
for (; i < str.size() && (str[i] >= '0' && str[i] <= '9'); i++) {
res = res * 10 + (str[i] - '0');
if (res >= INT_MAX && flag == 1) return INT_MAX;
if (res > INT_MAX && flag == -1) return INT_MIN;
}
return flag * res;
}
};
解题思路:二叉搜索树,其中序遍历有序。所以,当左右子树的值都大于根的值。则移动至根的右子树。当左右子树的值都小于根的值,则移动至根的左子树。若此时,一个结点的值比根大,另一个结点的值比根小,则 这个根就是两者最近祖先结点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)return NULL;
if(p->val > root->val && q->val > root->val)
return lowestCommonAncestor(root->right, p, q);
else if(p->val < root->val && q->val < root->val)
return lowestCommonAncestor(root->left, p, q);
else return root;
}
};
解题思路:递归查询两个节点 p 和 q,如果某个节点等于节点 p 或节点 q,则返回该节点的值给父节点。如果当前节点的左右子树分别包括 p 和 q 节点,那么这个节点必然是所求的解。如果当前节点有一个子树的返回值为 p 或 q 节点,则返回该值。(告诉父节点有一个节点存在其子树中)如果当前节点的两个子树返回值都为空,则返回空指针。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)return NULL;
if(root == p||root == q)return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left && right) return root;
return left ? left : right; // 只有一个非空则返回该指针,两个都为空则返回空指针
}
};