21、string的sort()、compare()
Anagram: 字符相同但字母顺序打乱的两个短语
class Solution {
public:
bool isAnagram(string s, string t) {
if(s.size()!=t.size())
return false;
sort(s.begin(),s.end());
sort(t.begin(),t.end());
if(!s.compare(t))
return true;
return false;
}
};
此题用字符哈希更快,解二:
class Solution {
public:
bool isAnagram(string s, string t) {
int charHash[26]={0}; //显性初始化
for(int i=0;iint)s[i]-97]++;
for(int j=0;j'a']--; //第二种写法 等效为charHash[(int)t[j]-97]--;
for(int k=0;k<26;k++){
if(charHash[k])
return false;
}
return true;
}
};
不显性初始化数组是未定义行为。
int charHash[5];
for(int i=0;i<5;i++)
cout<
运行结果为:
1968344277
1137358577
-2
1968247138
1968593852
可见charHash中元素为乱码
int charHash[5]={0};//可以将所有元素初始化为0
from 242. Valid Anagram
22、动态规划(DP)
拆分整数使得乘积最大
解法一:DP
dp[n]是n对应的最大拆分乘积
递推方程: dp[n]=max( i*dp[n-i], i*(n-i) ) (i从1到n-1)
边界条件:dp[2]=1
举例说明递推方程:求n=5的最大拆分。假设得到之前的部分正确结果,即dp[1]到dp[4]已知等于{1,1,3,4}。5的最大拆分为以下值中的最大值:1*dp[4]、1*4 、2*dp[3]、2*3、3*dp[2]、3*2、4*dp[1]、4*1
class Solution {
public:
int integerBreak(int n) {
int dp[n];
dp[1]=1;
dp[2]=1;
for(int i=3;i<=n;i++)
{
dp[i]=0;
for(int j=1;jreturn dp[n];
}
};
解法二:数学推导
由均值不等式 Hn≤Gn≤An≤Qn 知 Gn≤An 。
Gn=x1x1⋯xn−−−−−−−−√n≤An=x1+x2+⋯+xnn
即算数平均数大于几何平均数,所以拆分成几个相等的数乘积最大。
设把n拆分成 nx 个数,每个数都等于x。
转换为求他们的乘积 f(x)=xnx 的最大值,求导
f′(x)=(1−lnx)×nx2×xnx=0 得x=e时取到极大值。
e ≈ 2.7182, 所以取3时结果最大。
此题转化为能分出多少个3,正好分完或分到4时终止。
class Solution {
public:
int integerBreak(int n) {
if(n==2 || n==3) return n-1;
if(n==4) return 4; //此句多余,巧合
int res=1;
while(n>4){
res*=3;
n-=3;
}
res*=n;
return res;
}
};
from 343. Integer Break
23、主元素
主元素(Majority Element)是数组中出现次数严格大于 n2 的元素,例如{1,1,2}中的1为主元素 。若是{1,1,2,2}则不符合要求。
编程之美 O(n) 解法基于如下原理:
同时丢弃数组中两个不同的元素,主元素不变。
1、默认第一个元素为主元素res,count置1
2、从第二个元素开始遍历数组,如果res==nums[i] 时count++,否则count– 。如果count=0,则置下一个元素为res。如此遍历直到结束,res中保存的元素即为主元素。
class Solution {
public:
int majorityElement(vector<int>& nums) {
int res=nums[0],count=1;
for(int i=1;iif(res==nums[i])
count++;
if(res!=nums[i])
count--;
if(count==0)
res=nums[i+1];
}
return res;
}
};
from 169. Majority Element
24、大数BigInteger相加
非负num1与num2均保存在string中,求和。
每一位相加产生的进位只可能为0或1,若是1则置carry=1,加下一次循环的tempSum中。
class Solution {
public:
string addStrings(string num1, string num2) {
string res="";
int carry=0;
for(int i=num1.size()-1,j=num2.size()-1;i>=0 || j>=0 || carry>0; ){
int tempSum=0;
if(i>=0)
{
tempSum+=(int)(num1[i]-'0');
i--;
}
if(j>=0)
{
tempSum+=(int)(num2[j]-'0');
j--;
}
if(carry>0)
{
tempSum+=carry;
}
res=to_string(tempSum%10)+res;
if(tempSum/10==1)
carry=1;
else
carry=0;
}
return res;
}
};
循环次数等于较长串的长度,存在优化空间:处理完较短字符串,长串的剩余部分直接添加到结果即可。
from 415. Add Strings
25、二进制手表与位操作
二进制手表有四个hour灯(表示 0-11),6个min灯(0-59)
hour | 8 | 4 | 2 | 1 | . | . |
---|---|---|---|---|---|---|
min | 32 | 16 | 8 | 4 | 2 | 1 |
给定有几个灯亮,求可能的时间。
设置10个二进制位表示每一位的亮灭,前四位代表hour,后六位代表min。
从0到0x3ff(11 1111 1111)循环,依次判断是否符合条件。(有多余运算,实际不可能达到全1)
class Solution {
public:
vector<string> readBinaryWatch(int num) {
vector<string> res;
if(num>8 || num<0)
return res;
for(int i=0;i<=0x3ff;i++){
if(bitset<16> (i).count()==num) //如果遍历到1的个数符合条件的,取时间
getTime(i,res);
}
return res;
}
void getTime(int n, vector<string> &res){
string temp="";
int hour= (n & 0x3f0)>>6; // n & 0x3f0的作用是取前4位,并把后6位置0. >>6是右移6位,正数左右移均补零
int min= n & 0x3f; //取后6位
if(hour>11 || min > 59) //判断是否溢出,溢出则该组值不符合条件
return ;
temp=to_string(hour)+":";
if(min<10)
temp+="0"; //题目要求:分钟要补零,10:08合法,10:8非法
temp+=to_string(min);
res.push_back(temp);
}
};
from 401. Binary Watch
26、单链表反转
题目:将head->1->2->3 变为1<-2<-3<-head
设置三个指针,分别指向当前元素pNode、前一元素pre和后一元素follow。
/*
Definition for singly-linked list
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre=NULL, *pNode=head, *follow=NULL;
while(pNode){
follow=pNode->next;
pNode->next=pre;
pre=pNode;
pNode=follow;
}
return pre;
}
};
from 206. Reverse Linked List
27、二叉树的前中后序遍历
前序遍历又称先根遍历,首先访问根节点,然后先根访问左子树和右子树。
递归方法伪码:
// cout<< root->val <
void Order(TreeNode* root){
if(root!=NULL){
1
Order(root->left,res);
2
Order(root->right,res);
3
}
}
ac代码:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inOrder(root,res); //vector传参方法
return res;
}
void inOrder(TreeNode* root,vector<int> &res){
if(root!=NULL)
{
inOrder(root->left,res);
res.push_back(root->val);
inOrder(root->right,res);
}
}
};
from 94. Binary Tree Inorder Traversal
28、8皇后问题,n皇后问题
出处:http://blog.csdn.net/hackbuteer1/article/details/6657109
目前公认N皇后的最高效 算 法
class Solution {
public:
long sum=0,upperlim=1;
int totalNQueens(int n) {
upperlim = (upperlim << n) - 1;
nQueen(0,0,0);
return sum;
}
void nQueen(long row, long ld, long rd) {
if (row != upperlim)
{
// row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
// 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
// 也就是求取当前哪些列可以放置皇后
long pos = upperlim & ~(row | ld | rd);
while (pos) // 0 -- 皇后没有地方可放,回溯
{
// 拷贝pos最右边为1的bit,其余bit置0
// 也就是取得可以放皇后的最右边的列
long p = pos & -pos;
// 将pos最右边为1的bit清零
// 也就是为获取下一次的最右可用列使用做准备,
// 程序将来会回溯到这个位置继续试探
pos -= p;
// row + p,将当前列置1,表示记录这次皇后放置的列。
// (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
// (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
// 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
// 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
// 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
// 上产生的限制都被记录下来了
nQueen(row + p, (ld + p) << 1, (rd + p) >> 1);
}
}
else
{
// row的所有位都为1,即找到了一个成功的布局,回溯
sum++;
}
}
};
from 52. N-Queens II
29、灯泡开关
问题:n个灯泡进行如下操作
第一轮:打开所有灯泡 [1,1,1]
第二轮:将第2,4,6…的灯泡改变状态
第三轮:将第3,6,9…的灯泡改变状态
…
第n-1轮:将第n-1,2(n-1)…的灯泡改变状态
第n轮(特殊轮):将第n个灯泡改变状态
求最后几个亮。
TLE & MLE解法
n=99999正常 Runtime: 322 ms。n=9999999 case TLE
class Solution {
public:
int bulbSwitch(int n) {
int arr[n+1]={1},j;
for(int k=1;k<=n;k++)
arr[k]=1;
for(int i=2;ifor(j=i;j<=n;j+=i){
if(arr[j]==0)
arr[j]=1;
else if(arr[j]==1)
arr[j]=0;
}
}
if(arr[n]==0)
arr[n]=1;
else
arr[n]=0;
int count=0;
for(int k=1;k<=n;k++){
if(arr[k]==1)
count++;
}
return count;
}
};
O(1)解法
因数:c=a*b 则 a和b 都是c 的因数
4有3个因数,1,2和4, 1 * 4 =4 2 * 2=4 (奇数个)
5有2个因数,1和5。(偶数个)
因数都是成对出现的,事实上平方数n的两个因数 n−−√ 共占了一个位置,导致平方数的因数只有奇数个。
进行该过程后,只有平方数位置的灯泡是开启的(因为只翻转过1次)
int bulbSwitch(int n) {
return (int)sqrt(n); //开方向下取整
}
from 319. Bulb Switcher
30、不含相同字符单词的长度最大积
两两比较的方法出现TLE,查看结果发现该case长度达到了30万,就是针对此解法的,感受到LC的恶意。
class Solution {
public:
int maxProduct(vector<string>& words) {
if(words.size()==0)
return 0;
vector<int> res;
for(int i=0;i1;i++)
for(int j=i+1;jif(noSameChar(words[i],words[j]))
res.push_back(words[i].size()*words[j].size());
}
if(!res.size())
return 0;
int maxRes=0;
for(int k=0;kif(res[k]>maxRes)
maxRes=res[k];
}
return maxRes;
}
bool noSameChar(string a,string b){
int arry[26]={0};
for(int i=0;i'a']++;
for(int j=0;jif(arry[b[j]-'a']>0)
{
return false;}
}
return true;
}
};
AC解法,位操作比较字符串:
相较于第一种解法优化了相同字母检测,循环次数依旧不变。
int i=0;
i |=1<<5; //将i的二进制右数第五位(实际是第六位,从0计数即为第5位),置1
//分步骤解读:
// 1<<5 是1左移5位得到100000
// i=i | 100000 此时i=100000,
可以将words中的每个字符串转为掩码形式,例如words[0]=”abc”转为mask[0]=111,words[1]=def转为111000。 words[0] & words[1] 等于0,说明两字符串没有相同的字符。
class Solution {
public:
int maxProduct(vector<string>& words) {
int res=0;
if(words.size()==0)
return 0;
vector<int> mask(words.size(),0);
for(int i=0;ifor(int j=0;j1<<(words[i][j]-'a');
for(int i=0;i1;i++)
for(int j=i+1;jif(! (mask[i] & mask [j]) )
res=max(res, (int)(words[i].size()*words[j].size() ) );
}
return res;
}
};
from 318. Maximum Product of Word Lengths
31.找重复的数 [只读数组,空间O(1)]
问题:数组中包含N+1个数字,每个数字范围在1到n(含),找出重复的数字。
解法1:鸽巢原理
时间O(nlogn) 空间O(1)
三个抽屉放4只袜子,必然有一个抽屉至少两只。使用二分法选取中间的数mid,如果整个数组中小于等于mid的数大于mid个,则区间[0,mid)中含有重复的数,否则重复的数在中(mid,max)中。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int min=0, max=nums.size()-1,mid,cnt;
while(min<=max)
{
cnt=0;
mid=min+(max-min) /2;
for(int i=0;isize();i++)
{
if(nums[i]<=mid)
cnt++;
}
if(cnt>mid)
max=mid-1;
else
min=mid+1;
}
return min;
}
};
解法2. 快慢指针
时间O(n) 空间O(1)
利用数组和下标的映射关系。
例如数组nums[1,2,3]
1.从下标0出发,nums[0]=1,则下一步映射为1
2.nums[2]=3,下一步映射为3,此时到达结尾,结束映射,下标映射序列为0->1->3
考虑数组nums[2,1,2,3],其映射序列为0->2->0->2->.. ,出现了环,此时即存在重复元素。
此时问题转换为求环路起点。快慢指针从0开始,快指针每次映射两次,慢指针每次映射一次。当指针相遇时,保持慢指针不动。此时使用新指针从0开始,新慢两个指针每次映射一次,当两指针相遇时即为环起点(Floyd’s tortoise and hare algorithm)。
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slowPointer=0,fastPointer=0;
do {
slowPointer=nums[slowPointer];
fastPointer=nums[nums[fastPointer]];
}
while(slowPointer!=fastPointer);
int resPointer=0;
while(slowPointer!=resPointer)
{
slowPointer=nums[slowPointer];
resPointer=nums[resPointer];
}
return resPointer;
}
};
from 287.Find the Duplicate Number
32.最大字串和
解法1.Kadane algorithm
两个前提:
1.最大子串的任意前缀非负。即对于array[ 1,2,n ],设arry[i..j]为满足条件的和最大字串,则对于任意k∈(i,j)都满足array[i,k]>0。反证法,已知arry[i..j]为和最大子串,若array[i,k]<0, 则array[k,j]为最大子串与题设矛盾。
2.将数组切割为若干子串,使得除最后一个子串外,之前各子串的和均小于0,则此时满足条件的子串不可能跨越多个子串。例如array={−2, 1, −3, 4, −1, 2, 1, −5, 4} ,切割为{2} ,{1,-3},{4, −1, 2, 1, −5, 4} ,此时满足条件的子串在第三个子串中(该串本身,或为该子串的前缀子串)。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max = INT_MIN,sum=0;
/*
源于极限头文件limits.h,INT_MIN表示带符号int的最小值
*/
for(int i=0;iif(max < sum)
max=sum;
if(sum<0)
sum=0;
}
return max;
}
};
from 53. Maximum Subarray
33.最长子序列
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4 要求时间复杂度为O(n)
由于STL map基于红黑树,建立过程需要排序,导致超时。
故使用C++11 unordered_set作hash表,建立代价为O(n),查询代价为O(1)
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
if(!nums.size())
return 0;
unordered_set<int> hash_table(nums.begin(),nums.end());
int res = 1;
for(auto i :nums){
/*此处若使用for(auto i=0;i
if(hash_table.find(i)==hash_table.end()) //unordered_set::find 查找成功返回i的iter,查找失败则返回unordered_set::end (end为容器最后一个元素的下一个空间)
continue;
hash_table.erase(i);
int left = i-1,right = i+1;
while(hash_table.find(left)!=hash_table.end())
hash_table.erase(left--);
while(hash_table.find(right)!=hash_table.end())
hash_table.erase(right++);
res = max(res,right-left-1);
}
return res;
}
};
from 128. Longest Consecutive Sequence
34.合并有序数组
数组1中有足够空间放下两个数组,若从前向后合并会涉及大量的元素移动,此时选择从后向前合并。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1=m-1, p2=n-1,pos=m+n-1;
while(p1>=0 && p2>=0)
{
if(nums1[p1]>=nums2[p2])
{
nums1[pos]=nums1[p1];
p1--;
}
else
{
nums1[pos]=nums2[p2];
p2--;
}
pos--;
}
while(p1>=0) //p1数组没处理完
{
nums1[pos]=nums1[p1];
p1--;
pos--;
}
while(p2>=0)
{
nums1[pos]=nums2[p2];
p2--;
pos--;
}
}
};
from 88. Merge Sorted Array
35.有序数组去重
要求:在同一数组中操作,常量空间。返回元素个数, 数组中有效数据之后东西不管It doesn’t matter what you leave beyond the new length
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if ( nums.size()<2)
return nums.size();
int count=0;
for(int i=1; iif(nums[i] == nums[i-1])
count++;
else
nums[i-count]=nums[i];
}
return nums.size()-count;
}
};
from 26. Remove Duplicates from Sorted Array
36.旋转数组
For example, with n = 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4]
解法一:新数组
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int a[nums.size()];
for(int i=0;ifor(int k=0;k
解法二,三次reverse操作
1.reverse整个数组
2.reverse前k个元素
3.reverse剩下的元素
如[1,2,3,4,5,6,7],k=3
第一次反转:7,6,5,4,3,2,1
第二次反转:5,6,7,4,3,2,1
第三次反转:5,6,7,1,2,3,4
class Solution {
public:
void reverse(vector<int>& rNums,int begin,int end)
{
int temp;
while(beginvoid rotate(vector<int>& nums, int k) {
k%=nums.size();
reverse(nums,0,nums.size()-1);
reverse(nums,0,(k-1)<0?0:(k-1)%(nums.size()));
reverse(nums,k%(nums.size()),nums.size()-1 );
}
};
同一个类里的函数调用不用声明,跨类声明在最上方即可
from 189.189. Rotate Array
37.股票交易
数组中第i个元素表示第i天的股价
问题1:只允许买卖一次求最大利润
一次遍历即可
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()==0)
return 0;
int sum=0,low=prices[0];
for(int i=1;iif(prices[i]if( (prices[i]-low) > sum )
sum= prices[i]-low;
}
return sum;
}
};
问题2:允许买卖多次,但买入前必须卖出之前持有的(允许同一天同时买入卖出)
贪心法,只要后一项比前一项大,二者差值就可以算进利润。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int sum=0;
for(int i=1;iif(prices[i] > prices[i-1])
sum+=(prices[i]-prices[i-1]);
}
return sum;
}
};
from
121. Best Time to Buy and Sell Stock
122. Best Time to Buy and Sell Stock II
38.杨辉三角(帕斯卡三角)
输入层数,二维vector形式输出形态
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> res;
if(numRows==0)
return res;
vector<int> firstLine(1,1); // vector vec(n,i) 用n个i初始化容器
res.push_back(firstLine); //第一行形态固定
for(int i=0;i< numRows-1;i++)
{
vector<int> temp(1,1); // 最左边的1
int p=0;
for(int j=0;j<=i-1;j++)
{
temp.push_back(res[i][p]+res[i][p+1]);
p++;
}
temp.push_back(1); // 最右边的1
res.push_back(temp);
}
return res;
}
};
from 118. Pascal’s Triangle
39.求开方sqrt(x)
例如:输入100,输出10
暴力法:循环求 a2 , 当其大于x时,a-1即为解。使用long long或者 unsigned long long ,防止溢出。
牛顿迭代法:
使用切线逼近。
计算x2=n,令f(x)=x2−n,转化为求f(x)=0的根。
class Solution {
public:
int mySqrt(int x) {
if (x==0)
return 0;
double pre,p=1;
do{
pre=p;
p=x/(2*pre)+pre/2;
}
while(abs(p-pre)>0.1); //精度越高越准确
return (int)p;
}
};
致敬数学家!
from 69. Sqrt(x)
40. 装水最多的容器
非负数组[1,2,3], 表示坐标上三个点(0,1), (1,2), (2,3) ,过三个点做x轴的三条垂线,两条垂线加上x轴形成一个容器,求容器的最大面积。
贪心法:
假设找到了由ai和aj围成的最大容器S,此时若i的左边存在比ai大的数ai−1,此时S就不是最大容器,ai−1,aj围成的容易显然大于S。
故最终结果中i的左边点高度一定小于i,右边同理。
然后考虑x轴,从两边向中间靠拢,靠拢时注意优先从高度较短的一边收缩,同时要注意记录最大面积。
class Solution {
public:
int maxArea(vector<int>& height) {
int maxLeft=height[0],maxRight=height[height.size()-1];
int low=0,high=height.size()-1,i=low,j=high;
int area=0, maxArea=0;
while(low<=high)
{
if(maxLeftif(height[low]>maxLeft)
{
maxLeft=height[low];
i=low;
}
low++;
}
else
{
if(height[high]>maxRight)
{
maxRight=height[high];
j=high;
}
high--;
}
area=(j-i)*min(maxLeft,maxRight);
maxArea=area>maxArea?area:maxArea;
}
return maxArea;
}
};
from 11. Container With Most Water