Gray Code
给出参数n,将区间[0,2n-1]之间的数字,从0开始排列成格雷码。格雷码的特点为,相邻的两数字转换为二进制时,仅有一位不同。
根据格雷码的特点,相邻数字只有一位不同,共有2n种不同情况,因此可以设计dfs对所有情况进行遍历。因为必须遍历完区间内全部数字,当n=16时,迭代的深度就达到了1e5,100%GG。
class Solution {
public:
vector<int> ans;
vector<int> grayCode(int n) {
int lim = 1<<n;
vector<bool> vis(lim,false);
vector<vector<int> > adj;
for(int i=0;i<lim;i++){
vector<int> tmp;
for(int dig = 0;dig<n;dig++){
tmp.push_back(i^(1<<dig));
}
adj.push_back(tmp);
}
vis[0] = true;
if(dfs(0,1,1<<n,adj,vis))
ans.push_back(0);
vector<int> reAns;
for(int i=lim-1;i>=0;i--)
reAns.push_back(ans[i]);
return reAns;
}
bool dfs(int pos,int count,int lim,vector<vector<int> > adj,vector<bool> vis){
int len = adj[pos].size();
for(int i=0;i<len;i++){
int node = adj[pos][i];
if(vis[node]){
if(count==lim){
return true;
}
}else{
vis[node] = true;
if(dfs(node,count+1,lim,adj,vis)){
ans.push_back(node);
return true;
}else{
vis[node] = false;
}
}
}
return false;
}
};
于是进一步改进,把递归改为循环实现。
这里利用stack先入后出的特性实现递归的循环实现。假设有如下递归过程
部分递归顺序如下
现仿照递归,每次遍历一个节点将全部子节点压入栈中,每次取栈顶元素作为下一个遍历对象,当栈中某元素被访问两次后将该元素出栈。
实例过程如下图所示。这个图有点小问题,AⅠ1和BⅠ1之间并不是相邻的,中间还有A的其他子节点,把这些节点补上的话能更直观一点。
由此利用stack将递归改为循环,代码如下
class Solution {
public:
vector<int> grayCode(int n) {
int lim = 1<<n;
vector<bool> vis(lim,false);
vector<int> ans;
stack<int> S;S.push(0);
while(ans.size()<lim){
int pos = S.top();
if(vis[pos]){
S.pop();ans.pop_back();
vis[pos] = false;
continue;
}else{
vis[pos] = true;
ans.push_back(pos);
for(int dig=0;dig<n;dig++){
int node = pos^(1<<dig);
if(!vis[node])
S.push(node);
}
}
}
return ans;
}
};
给出一个数组arr,找出该数组中距离x最近的前k个元素。
定义数字a与数字x的距离为
∣
a
−
x
∣
|a-x|
∣a−x∣,若数字a与数字b和x距离相同,则较小的数字被认为距离更近。
思路上比较简单,计算每个元素与x的距离并排序,取排序结果的前k个即可。
这里主要说一下快速排序的递归实现,每次递归调用将数组分成三部分。以数组中某元素作为基准,数组左侧元素均小于基准数,右侧均大于基准数。
步骤如下
- 选取最左侧元素作为基准数
- 从数组右侧遍历找到第一个小于基准数的元素,交换到右侧
- 从数组左侧遍历找到第一个大于基准数的元素,交换到左侧
- 重复2、3,直到左侧遍历指针与右侧遍历指针相遇
- 左侧遍历指针与右侧遍历指针相遇,填入基准数,对基准数左右两侧子数组进行排序
当进行到没有子数组时结束排序。
class Solution {
public:
bool cmp(int a,int b,int x){
int d1 = abs(a-x),d2 = abs(b-x);
if(d1!=d2)
return d1<d2;
else
return a<=b;
}
void quikSort(vector<int>& arr,int l,int r,int x){
if(l>=r)
return;
int star = l,finish = r;
int num = arr[star];
while(star<finish){
while(star<finish && cmp(num,arr[finish],x))
finish --;
if(star<finish)
arr[star] = arr[finish];
while(star<finish && cmp(arr[star],num,x))
star ++;
if(star<finish)
arr[finish] = arr[star];
}
arr[star] = num;
quikSort(arr,l,star-1,x);
quikSort(arr,star+1,r,x);
}
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
quikSort(arr,0,arr.size()-1,x);
vector<int> ans(arr.begin(),arr.end());
ans.resize(k);
sort(ans.begin(),ans.end());
return ans;
}
};
Max Sum of Rectangle No Larger Than K
求小于k的最大子矩阵元素和
brute force :遍历每一个包含元素matrix[i][j]的子矩阵
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
int n = matrix.size(),m = matrix[0].size();
int sum[n+1][m+1];
for(int i=0;i<=m;i++)
sum[0][i] = 0;
for(int i=1;i<=n;i++){
sum[i][0] = sum[i-1][0];
for(int j=1;j<=m;j++){
sum[i][j] = sum[i][j-1] + matrix[i-1][j-1];
}
for(int j=1;j<=m;j++)
sum[i][j] += sum[i-1][j];
}
int ans = INT_MIN;
for(int x2=0;x2<n;x2++){
for(int y2=0;y2<m;y2++){
for(int x1=0;x1<=x2;x1++){
for(int y1=0;y1<=y2;y1++){
int tmp = sum[x2+1][y2+1] - sum[x1][y2+1] - sum[x2+1][y1] + sum[x1][y1];
if(tmp<=k)
ans = max(ans,tmp);
}
}
if(ans==k)
break;
}
if(ans==k)
break;
}
return ans;
}
以上n2做法过于残忍,再给一个更加优美的方案。这里推荐leetcode官方题解,写的非常详细,通俗易懂。
首先考虑子矩阵只有一行的情况,这样即使子矩阵为多行的情况下,也能通过列作和化为一维。
记sum[i]表示前i个元素和。若当前位置i的前缀和为S,那么想让以位置i结束的子矩阵和小于k,则至少移除和为
x
=
S
−
k
x = S - k
x=S−k的部分。为了快速搜索x,将所有前缀和放入有序集和中进行二分搜索。
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
int n = matrix.size(),m = matrix[0].size();
vector<int> cum(m+1,0),store(m+1,0);
int ans = INT_MIN;
for(int i=0;i<n;i++){
for(int row = i;row>=0;row--){
set<int> sum;
sum.insert(0);
for(int j=1;j<=m;j++){
store[j] += matrix[row][j-1];
cum[j] += cum[j-1] + store[j];
int S = cum[j];
auto found = sum.lower_bound(S - k);
if(found!=sum.end()){
int val = S - *found;
if(val<=k)
ans = max(val,ans);
}
sum.insert(S);
}
for(int _=0;_<=m;_++)
cum[_] = 0;
}
for(int _=0;_<=m;_++)
store[_] = 0;
}
return ans;
}
};
Count Vowels Permutation
基础dp,状态方程见题意
class Solution {
public:
const int MOD = 1e9+7;
int countVowelPermutation(int n) {
vector<vector<long long> > dp(n,vector<long long>(5,0));
for(int i=0;i<5;i++)
dp[0][i] = 1;
for(int i=1;i<n;i++){
dp[i][0] = (dp[i][0] + dp[i-1][1]) % MOD;
dp[i][1] = (dp[i][1] + dp[i-1][0] + dp[i-1][2]) % MOD;
for(int _=0;_<5;_++)
if(_!=2)
dp[i][2] = (dp[i][2] + dp[i-1][_]) % MOD;
dp[i][3] = (dp[i][3] + dp[i-1][2] + dp[i-1][4]) % MOD;
dp[i][4] = (dp[i][4] + dp[i-1][0]) % MOD;
}
long long ans = 0;
for(int i=0;i<5;i++)
ans = (ans + dp[n-1][i]) % MOD;
return (int)ans%MOD;
}
};
class Solution {
public:
vector<vector<int>> matrixReshape(vector<vector<int>>& mat, int r, int c) {
int m = mat.size(),n = mat[0].size();
if(n*m == r*c){
vector<vector<int> > ans (r,vector<int>(c,0));
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
int index = i*n + j;
int row = index / c;
int col = index - row*c;
ans[row][col] = mat[i][j];
}
}
return ans;
}else{
return mat;
}
}
};
Reduce Array Size to The Half
贪心,按数字出现次数降序排序,依次删除出现次数最多的
class Solution {
public:
int minSetSize(vector<int>& arr) {
int n = arr.size();
int aim = n/2;
priority_queue<int> heap;
map<int,int> sta;
for(int ele : arr){
sta[ele] ++;
}
for(auto it=sta.begin();it!=sta.end();it++){
heap.push(it->second);
}
int ans = 0;
while(n>aim){
int num = heap.top();
heap.pop();
n -= num;
ans++;
}
return ans;
}
};
Kth Smallest Element in a Sorted Matrix
Maximum Length of Repeated Subarray
找出两个序列的最长公共子列。
记dp[i][j] 为以序列nums1位置i结尾 与 以序列nums2位置j结尾 的最长公共子列长度。当nums[i]==nums[j]时,从上一状态进行转移,dp[i][j] = dp[i-1][j-1]+1。
因为序列中数字范围较小,将nums2中出现的数字与其位置进行映射,便于查找。
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
int len1 = nums1.size(),len2 = nums2.size();
vector<int> sta[128];
for(int i=0;i<len2;i++){
sta[nums2[i]].push_back(i);
}
vector<vector<int> > dp(len1,vector<int>(len2,0));
//ini
for(int i=0;i<sta[nums1[0]].size();i++){
int j=sta[nums1[0]][i];
dp[0][j] = 1;
}
int ans = 0;
for(int i=1;i<len1;i++){
int ele = nums1[i];
for(int ind=0;ind<sta[ele].size();ind++){
int j = sta[ele][ind];
if(j!=0){
dp[i][j] = dp[i-1][j-1] + 1;
}else{
dp[i][j] = 1;
}
ans = max(ans,dp[i][j]);
}
}
return ans;
}
};
Longest Increasing Subsequence
经典最长单调上升子列LIS问题,dp入门题目之一笨比如我还是设计不出nlogn的
首先说一下比较简单的dp设计,复杂度n2。记dp[i]表示以nums[i]结尾的最长上升列,在遍历数组nums时,只需查找 当前位置pos前,结尾元素小于nums[pos] 的所有子列中最长的一个。
for(int i = 0;i<pos;i++)
if(nums[i]<nums[pos])
dp[pos] = max(dp[pos],dp[i]+1)
如此遍历数组就能得到LIS了。
但是在遍历过程中是有很多重复的
// fig.replace
为了避免重复访问,需要在 所有位置pos前,最大的dp[i]值 中快速查找半天没想出来怎么设计这么个结构呜呜呜
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> len;
len.push_back(nums[0]);
int ans = 1;
for(int i=1;i<n;i++){
int ele = nums[i];
if(ele>len.back()){
len.push_back(ele);
ans ++;
}else{
auto it = lower_bound(len.begin(),len.end(),ele);
*it = ele;
}
}
return ans;
}
};
Find Median from Data Stream
multiset实践应用。
class MedianFinder {
public:
/** initialize your data structure here. */
MedianFinder() {
len = 0;
isEven = true;
}
void addNum(int num) {
store.insert(num);
isEven = !isEven;
if(len==0)
it = store.begin();
else{
int mid = *it;
if(num >= mid && !isEven) it = next(it);
if(num < mid && isEven) it = prev(it);
}
len ++;
}
double findMedian() {
if(isEven){
return ((double)(*it+*next(it)))/2;
}else{
return (double)*it;
}
}
private:
int len;
bool isEven;
multiset<int> store;
multiset<int>::iterator it ;
};
Isomorphic Strings
考察字符串s是否能映射到字符串t。注意s中两个字母不能映射到t的同一个字母上,测试数据如s=“abc”,t=“iii”
class Solution {
public:
bool isIsomorphic(string s, string t) {
vector<int> mapping(130,-1);
vector<bool> used(130,false);
int len = s.size();
bool ans = true;
for(int i=0;i<len;i++){
int c1 = s[i],c2 = t[i];
int mValue = mapping[c1];
if((mValue==c2) || (!used[c2] && mValue<0)){
mapping[c1] = c2;
used[c2] = true;
}else{
ans = false;
break;
}
}
return ans;
}
};
Find Peak Element
太懒了写个双指针,打卡完毕
class Solution {
public:
int findPeakElement(vector<int>& arr) {
int n = arr.size();
if(n==1 || arr[0]>arr[1])
return 0;
if(n-2 >= 0 && arr[n-2]<arr[n-1])
return n-1;
int left = 1,right = n-2;
int ans;
while(left<=right){
if(isPeak(arr[left],arr[left-1],arr[left+1])){
ans = left;
break;
}else{
left += getStep(arr[left],arr[left-1],arr[left+1]);
}
if(isPeak(arr[right],arr[right+1],arr[right-1])){
ans = right;
break;
}else{
right -= getStep(arr[right],arr[right+1],arr[right-1]);
}
}
return ans;
}
bool isPeak(int a,int b,int c){
return a>b && a>c;
}
int getStep(int a,int b,int c){
return 1;
}
};
Custom Sort String
比较简单的构造题。把统计str中各字母出现的次数,按order给的顺序排列,再把order中没出现的加上
class Solution {
public:
string customSortString(string order, string str) {
int len1 = order.size(), len2 = str.size();
vector<bool> appear(26,false);
map<char,int> sta;
string sb1;
for(int i=0;i<len1;i++){
int index = order[i] - 'a';
appear[index] = true;
}
for(int i=0;i<len2;i++){
sta[str[i]] ++;
if(!appear[str[i]-'a'])
sb1 += str[i];
}
string sb2;
for(int i=0;i<len1;i++){
char c = order[i];
while(sta[c] -- )
sb2 += c;
}
return sb2 + sb1;
}
};
Valid Triangle Number
在给出的数组中,找出所有可以构成三角形的不同三元组(值相同位置不同视为不同)。
定二搜一。假设b<a<c,根据三角形性质应满足如下方程
{
a
+
b
>
c
a
−
b
<
c
\begin{cases} a + b > c \\ a - b < c \end{cases}
{a+b>ca−b<c
故a b 值确定,则c范围确定,对nums排序,查找第三条可行边的范围。
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(),nums.end());
int ans = 0;
int len = nums.size();
for(int i=0;i<len;i++){
int b = nums[i];
for(int j=i+1;j<len;j++){
int a = nums[j];
int lower = a-b+1,upper = a+b-1;
if(upper<lower)
continue;
int ind1 = (lower_bound(nums.begin()+j+1,nums.end(),lower) - nums.begin());
int ind2 = (upper_bound(nums.begin()+j+1,nums.end(),upper) - nums.begin());
ans += ind2 - ind1;
}
}
return ans;
}
};
Three Equal Parts
突破点在于arr中1的个数,如果arr中1的个数是3的倍数,那么该数组可能分为三部分,否则不可能。
当数组满足这个必要不充分条件,在按1个数划分的基础上,根据0的位置进行调整,最后比对三部分是否一致
class Solution {
public:
vector<int> threeEqualParts(vector<int>& arr) {
int len = arr.size();
int cnt = 0;
for(auto ele : arr){
cnt += (ele==1);
}
if(cnt%3 != 0){
return vector<int>(2,-1);
}
if(cnt==0){
vector<int> ans;
ans.push_back(0);
ans.push_back(len-1);
return ans;
}
vector<int> ans;
int ind = cnt/3,tmp = 0;
for(int i=0;i<len;i++){
tmp += (arr[i]==1);
if(tmp==ind){
ans.push_back(i);
break;
}
}
tmp = 0;
for(int i=len-1;i>=0;i--){
tmp += (arr[i]==1);
if(tmp==ind+1){
ans.push_back(i+1);
break;
}
}
int suf0 = 0;
tmp = len-1;
while(arr[tmp]==0){
suf0 ++;
tmp --;
}
ans[0] += suf0,ans[1] += suf0;
int leading0 = 0;
tmp = 0;
while(arr[tmp]==0){
leading0 ++;
tmp ++;
}
bool isAns = true;
for(int i=0;i<=ans[0]-leading0;i++){
if(arr[ans[0]-i]!=arr[ans[1]-i-1] || arr[ans[0]-i]!=arr[len-1-i]){
isAns = false;
break;
}
}
if(isAns){
return ans;
}else
return vector<int> (2,-1);
}
};
Reverse Nodes in k-Group
翻转链表的plus版,每k个一组进行翻转。
大概过程如图所示虽然画的我也不太明白
经过k个节点后讲链表翻转,并接到原链表上,这样保证在不足k个节点时,该部分仍按原序排列,不被翻转。
如果接下来有不少于k个节点,再进行翻转,注意记录翻转链表的头尾,再将这部分接到上一个翻转链表上
class Solution {
public:
ListNode* ans;
ListNode* reverseKGroup(ListNode* head, int k) {
start = head;
first = true;
dfs(head,1,k);
return ans;
}
void reserve(ListNode* now,ListNode* pre,int num,int k){
if(num==k){
if(first){
first = false;
ans = now;
}
tmpHead = now;
}else
reserve(now->next,now,num+1,k);
now->next = pre;
}
void dfs(ListNode* node,int num,int k){
if(node==NULL){
return;
}
if(num==k){
reserve(start,node->next,1,k);
if(tail!=NULL)
tail->next = tmpHead;
tail = start;
start = start->next;
dfs(start,1,k);
}else{
dfs(node->next,num+1,k);
}
}
private:
ListNode* start;
ListNode* tail;
ListNode* tmpHead;
bool first;
};
Lowest Common Ancestor of a Binary Search Tree
由于限定为二叉搜索树,所以不必真的去了解LCA算法看了之后是真几巴难
如果节点node是p,q的ancestor,那么p,q一定位于node的同一侧,也就是都在左子树或右子树。又因为二叉搜索树的性质,只需要比较node和p,q节点值的大小,就可以快速判断p,q是否在同一侧。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
int rootVal = root->val;
int qVal = q->val,pVal = p->val;
if(rootVal > pVal && rootVal > qVal)
return lowestCommonAncestor(root->left,p,q);
if(rootVal < qVal && rootVal < pVal)
return lowestCommonAncestor(root->right,p,q);
return root;
}
};
生成原数组的随机数组,Knuth洗牌算法。
下面先给一个伪随机算法:将位置i的元素,等概率的与其他元素(包括自身)交换。
for(int i=0;i<len;i++){
int pos = rand()%len;
swap(nums[pos],nums[i]);
}
这种算法会生成lenlen种排列方式,但实际上长度为len的集和全排列只有len!种,而lenlen>len!,也就是说上述算法产生了一些重复排列。
假如每个排列都重复了t次,那么还是符合题意,使得每种排列等概率出现,但是lenlen和len!应该是不存在什么倍数关系。
所以我们需要删掉重复出现的排列,在这里可以模拟全排列的计算过程,控制位置i可以交换元素的范围,从而产生所有排列等概率出现的结果集。
class Solution {
public:
vector<int> copy;
Solution(vector<int>& nums) {
copy = vector<int>(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() {
vector<int> ans(copy);
int len = ans.size();
for(int i=0;i<len-1;i++){
int add = rand()%(len-i);
swap(ans[i],ans[i+add]);
}
return ans;
}
};
Push Dominoes
当前为’.‘时,不处理,跳过
当前为’L’时,查询是否有’R’未处理,如果有根据长度判断中间位置情况,没有推到头
当前为’R’,查询是否有’R’未处理,没有记录位置,有推到上一个’R’的位置
class Solution {
public:
string pushDominoes(string str) {
int len = str.size();
string ans;
for(int i=0;i<len;i++)
ans += '.';
int pos;
int last = -1;
for(int i=0;i<len;i++){
switch(str[i]){
case 'L':
if(last<0){
pos = i;
while(pos>=0 && ans[pos]=='.'){
ans[pos --] = 'L';
}
}else{
int range = (i-last+1)/2;
for(int _=0;_<range;_++){
ans[last+_] = 'R';
ans[i-_] = 'L';
}
last = -1;
}
break;
case 'R':
if(last<0){
last = i;
}else{
pos = last;
while(pos<i){
ans[pos ++] = 'R';
}
last = i;
}
break;
case '.':
break;
}
}
if(last>=0)
while(last<len)
ans[last ++] = 'R';
return ans;
}
};
Partition Array into Disjoint Intervals
将给出的数组分为两部分,left最大值小于right最小值。
记left[i]为[0,i]区间内最大值,right[i]为[i,len)最小值,判断条件如题意。
class Solution {
public:
int partitionDisjoint(vector<int>& nums) {
int len = nums.size();
vector<int> left(nums);
vector<int> right(nums);
for(int i=1;i<len;i++){
left[i] = max(left[i],left[i-1]);
}
for(int i=len-2;i>=0;i--){
right[i] = min(right[i],right[i+1]);
}
int ans = 1;
for(;ans<len;ans++){
if(left[ans-1]<=right[ans])
break;
}
return ans;
}
};
Binary Tree Pruning
如果当前节点node子树不存在值为1的节点,删除该子树。
利用dfs查找值为1的节点,如果存在返回该节点,不存在返回nullptr。父节点根据子树查询信息判断是否保留子树。
当且仅当左右子树为nullptr且当且节点node值不为1,表示以node为root的子树节点全为0,不符合题意,返回nullptr;否则表示以node为root的子树节点不全为0,应该返回该节点
class Solution {
public:
TreeNode* pruneTree(TreeNode* node){
if(node==nullptr)
return nullptr;
node->left = pruneTree(node->left);
node->right = pruneTree(node->right);
if(node->left==nullptr && node->right==nullptr && node->val == 0){
return nullptr;
}else{
return node;
}
}
};