编程日记,尽量保证每天至少3道leetcode题,仅此记录学习的一些题目答案与思路,尽量用多种思路来分析解决问题,不足之处还望指出。标红题为之后还需要再看的题目。
Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.
Example:
For num = 5 you should return [0,1,1,2,1,2].
题意:找出0-n之间的每个数字的包含1的位数。
思路:
1、题目也说了,逐个去找很简单。
代码:
C++
class Solution {
public:
int countBits1(int n)
{
int cnt = 0;
while(n)
{
n = n&(n-1);
cnt++;
}
return cnt;
}
vector<int> countBits(int num) {
vector<int > res;
for(int i=0;i<=num;i++)
{
res.push_back(countBits1(i));
}
return res;
}
};
结果:92ms
2、找一下规律,可以用DP的思想。
dp[0] = 0;
dp[1] = dp[0] + 1;
dp[2] = dp[0] + 1;
dp[3] = dp[2] +1;
dp[4] = dp[0] + 1;
dp[5] = dp[4] + 1;
dp[6] = dp[4] + 1;
dp[7] = dp[6] + 1;
dp[8] = dp[0] + 1;
初次看到这个东西可能会觉得奇怪,因为不知道括号里面是什么东西。但是,如果事先告诉dp[n] = dp[n&(n-1)]+1这个式子可能就会好理解一点了。因为n&(n-1)这个操作之后可以将数字中的1减少1位,也就是说dp[n] = dp[n&(n-1):减少1个1之后的状态值]+1。
很好理解,假设我需要求解n有多少个1,是不是等价于n去掉一个1之后的那个数(无论什么数不重要,总之比n少一个1就对了)的1的位数加上1。
C++
class Solution {
public:
vector<int> countBits(int num) {
vector<int> dp(num+1,0);
for(int i=1;i<=num;i++)
{
dp[i] = dp[i&(i-1)]+1;
}
return dp;
}
};
结果:92ms
Given an array of integers, every element appears twice except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
题意:给一个整型数组,除了一个数只出现了一次,其他数全部出现了两次,找出那个只出现过一次的数。
思路:
1、很显然,可以用一个额外的空间去存储信息,等到所有数字都统计完了直接遍历一次就知道
代码:
C++
unordered_set<int> mySet;
for(int i = 0;i < nums.size();++i){
if(mySet.find(nums[i]) == mySet.end()) mySet.insert(nums[i]);
else mySet.erase(nums[i]);
}
auto it = mySet.begin();
return *it;
结果:44ms
2、但是稍加思考我们可以知道,如果是异或操作那么同一个数出现2次异或之后的值为0。那么我们只要把所有的数都取异或操作那么剩下的就是那个只出现一次的值。
C++
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
for(int i=0;i<nums.size();i++)
res ^=nums[i];
return res;
}
};
结果:20ms
Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.
For example:
Given nums = [1, 2, 1, 3, 2, 5], return [3, 5].
Note:
1、The order of the result is not important. So in the above example, [5, 3] is also correct.
2、Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?
题意:统计一个数组中2个只出现一次的值。
思路:
花费O(N)的空间,显然可以和上面一题的做法一样,只是最后返回一个改成了2个而已。效率上就比较差了。
C++
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
vector<int> res;
set<int> tmp;
for(int i=0;i<nums.size();i++)
{
if(tmp.find(nums[i])==tmp.end()) tmp.insert(nums[i]);
else tmp.erase(nums[i]);
}
for(auto it=tmp.begin();it!=tmp.end();it++)
res.push_back(*it);
return res;
}
};
结果:52ms
2、首先,我们知道有两个数是不同的,在这个基础上我们可以得到这两个数字异或之后的结果。得到这个结果c怎么逆推回这两个数a、b呢?首先我们来观察一个实例。 a = 101 b = 011 c = a^b = 110 。我们希望能找到a与b的一个不同的位,这样就可以用来区分a、b。至于为什么这样可以,另外一个栗子:
假如我们有10个数除了a、b之外两两相同。假如a、b的第0位不同,那么我们可以根据这10个数第0位是0还是1来将数划分为两组。第0位为0的那一组数之间相互做异或,除了a之外的所有数字异或之后为0,这样最后剩下来的就是a,b也是同样的道理。
这个时候问题就转化为如何找到a、b不同的位。光看110我们知道第0位和第1位不同。假设c有5位,我们可以通过c&(c-1)这个操作使得c减少一个1,这个时候再与原来的c做一次异或,变化的那一位1可以作为我们之前要找到的那个1位。(类似one-hot一样的编码,一组0中有1个1,之后如何找包含这位为1的元素只需要做一次与操作即可)。
C++
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
vector<int> res;
int n = 0;
int a = 0;
int b = 0;
//n为我们所求的a、b异或之后的值
for(int i=0;i<nums.size();i++)
n^=nums[i];
//diff是a、b不同的一位,我们以此来分开a、b到两个不同的组中去
int diff = (n&(n-1))^n;
for(int i=0;i<nums.size();i++)
{
if(nums[i]&diff) a^=nums[i];
else b^=nums[i];
}
res.push_back(a);
res.push_back(b);
return res;
}
};
结果:24ms
Given an array of n integers where n > 1, nums, return an array output such that output[i] is equal to the product of all the elements of nums except nums[i].
Solve it without division and in O(n).
For example, given [1,2,3,4], return [24,12,8,6].
题意:一个数组除去自身元素的乘积。不能用除法,O(N)复杂度。
思路:
我们可以用一个数组保存从前到后所有元素的乘积和从后到前所有元素的乘积。基于这个的基础上,每次我们需要输出一个数的时候我们可以利用到我们之前创建的这两个数组来计算。不过这样的空间开销多了两个数组。需要O(N)的空间复杂度。
栗子:
[1,2,3,4]
fromBegin:[1,1,2,6]
fromEnd:[1,4,12,24]
res:[24,12,8,6]
对应res的第i位就是fromBegin的第i位和fromEnd的第n-i位相乘。
代码:
C++
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> res(n);
if(n<=0) return res;
vector<int> fromBegin(n);
vector<int> fromEnd(n);
fromBegin[0] = 1;
fromEnd[0] = 1;
for(int i=1;i<nums.size();i++)
{
fromBegin[i] = nums[i-1]*fromBegin[i-1];
fromEnd[i] = nums[n-i]*fromEnd[i-1];
}
for(int i=0;i<n;i++)
res[i] = fromBegin[i]*fromEnd[n-1-i];
return res;
}
};
结果:64ms
2、可是真的有必要保留两个额外的数组吗?不妨再思考一下
[1,2,3,4]
fromBegin:[1,1,2,6]
fromEnd:[1,4,12,24]
res:[24,12,8,6]
从前向后遍历一次,用一个数字fromBegin来表示第i位前的数字的乘积,另一个数字fromEnd表示后i位数字的乘积。当求到第i位的时候res[i] = fromBegin ,res[n-1-i]=fromEnd。
其实简单来说就是每一位i等于前i个乘积(0~i-1不包括i)和后n-1-i(i+1~n-1),而这两个数就是做这件事的。
class Solution {
public:
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> res(n,1);
if(n<=0) return res;
//fromBegin的意义是求[0,i)位之间的乘积;fromEnd是求(n-1-i,n-1]之间的乘积
int fromBegin = 1;
int fromEnd = 1;
for(int i=0;i<n;i++)
{
//求第i位和倒数第i位
res[i]*=fromBegin;
fromBegin *=nums[i];
res[n-1-i] *=fromEnd;
fromEnd *=nums[n-1-i];
}
return res;
}
};
结果:64ms