终于把LeetCode免费题目都做完了,接下来要做第二遍,想把第一遍不会做的题目整理一下思路,为接下来的google面试准备一下,万一过了呢,哈哈。
315 Count of Smaller Numbers After Self
题意:计算每个元素右面有多少个元素小于它
从后向前遍历,维护一个排好序的数组,每次插入新元素时确定其在有序数组的位置,就是要求的右面有多少个元素小于它。在插入新元素的时候就可以用二分了。
int binarySearch(vector<int>& nums, int target){
int low = 0;
int high = nums.size()-1;
while (low<=high){
int mid = low+((high-low)>>1);
if (nums[mid]<target) low = mid+1;
else high = mid - 1;
}
return low;
}
368 Largest Divisible Subset
题意:给定一个数组,找出可整除的最大集合,也就是在这个集合中所有数字都能互相整除。
思路:比较基本的dp题,但要保存每个数的上一个可整除的数,需要维护一个parent数组
vector<int> largestDivisibleSubset(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<int> ret;
int n = nums.size();
if (n==0) return ret;
vector<int> dp(n,1);
vector<int> parent(n,-1);
int maxP = 0;
int maxL = -1;
for (int i = 1; i < n; ++i){
for (int j = 0; j < i; ++j){
if (nums[i]%nums[j]==0 && dp[i]<dp[j]+1){
dp[i] = dp[j]+1;
parent[i] = j;
if (dp[i]>maxL){
maxP = i;
maxL = dp[i];
}
}
}
}
while (maxP!=-1){
ret.push_back(nums[maxP]);
maxP = parent[maxP];
}
reverse(ret.begin(),ret.end());
return ret;
}
372 Super Pow
题意:求a的b次方,b是一个很大的数,用数组保存
思路:递归+快速幂取模。设b为[1,2,3],
superPow(a,b)=a123=a120∗a3=(a12)10∗a3
,b弹出最后一个元素后变为[1,2],即可递归求解。
int quickMod(int a, int b){
int ans = 1;
a = a%1337;
while (b){
if (b & 0x1) ans = (ans*a)%1337;
b >>= 1;
a = (a*a)%1337;
}
return ans%1337;
}
int superPow(int a, vector<int>& b) {
if (b.size()==0) return 1;
int last = b.back();
b.pop_back();
return (quickMod(superPow(a,b),10)*quickMod(a,last))%1337;
}
375 Guess Number Higher or Lower II
题意:猜数字,保证赢的情况下总金额最少
思路:基本dp,但是一维dp不行,必须dp[start][end]保存
int getMoneyAmount(int n) {
vector<vector<int>> table(n+1,vector<int>(n+1,0));
return dp(table,1,n);
}
int dp(vector<vector<int>> &table, int s, int e){
if (s>=e) return 0;
if (table[s][e]!=0) return table[s][e];
int amin = INT_MAX;
for (int i=s;i<=e;i++){
int tmp = i+max(dp(table,s,i-1),dp(table,i+1,e));
amin = min(amin,tmp);
}
table[s][e]=amin;
return amin;
}
330 Patching Array
题意:给一数组和整数n,确保数组中的数能组合出1~n的所有整数,找出缺失的个数
思路:维护一个变量保存现在能累加到的值a,也就是1到a所有值都已经可以得到,那么miss = a+1即是缺失的值。对下一个数字num[i],如果其小于等于a+1,那么我们依然可以利用已有的数获得它,a也就更新为a+num[i],miss=miss+num[i],如果其大于a+1,那么说明a+1是我们获取不到的,就是我们缺失的值,a更新为a+a+1,miss=miss+a+1=miss+miss;
坑:miss类型要注意,如果n为INT_MAX,那么miss必须设置为long才可以过corner case.所以不管任何题目都要做边界测试,INT_MAX,INT_MIN必须能过才行。
int minPatches(vector<int>& nums, int n) {
long miss = 1;
int i = 0;
int count = 0;
while (miss <= n){
if (i < nums.size() && nums[i] <= miss){
miss += nums[i++];
}else{
count++;
miss += miss;
}
}
return count;
}
264 Ugly Number II
题意:找出第n个丑数,丑数为因数只包含2,3,5的数字,比如1,2,3,4,5,6,8,9,10
思路:丑数*2,3,5仍然是丑数,维护三个变量p2,p3,p5,分别表示乘以2,3,5的三个丑数在ugly数组中的位置。最开始都指向第一个丑数1,分别乘以2,3,5,取乘积最小的2作为下一个丑数放入ugly数组,同时p2++
int nthUglyNumber(int n) {
int p2,p3,p5;
p2 = p3 = p5 = 0;
vector<int> ugly(n);
ugly[0]=1;
for (int i=1;i<n;++i){
int tmp = min(ugly[p2]*2,min(ugly[p3]*3,ugly[p5]*5));
if (tmp == ugly[p2]*2) p2++;
if (tmp == ugly[p3]*3) p3++;
if (tmp == ugly[p5]*5) p5++;
ugly[i] = tmp;
}
return ugly[n-1];
}
363 Max Sum of Rectangle No Larger Than K
题意:找出一个矩形区域,使得其和为不大于K的最大值
思路:此题可拆分成两题,第一是矩形区域的遍历,维护两个变量left,right分别表示左右列,再保存一个sum的数组,维数等于矩形的行数。sum[i]表示left至right这块区域内从迪一行累加到第i行的和,那么sum[j] - sum[i]即可表示行i到行j这段区域的和。
第二题是给定一个数组,求子数组的和不大于K的最大值。方法是维护一个set,对于每个新插入的值sum[j],先计算大于等于sum[j]-k的最小值,然后k-那个最小值就是不大于k的最大值。具体见https://www.quora.com/Given-an-array-of-integers-A-and-an-integer-k-find-a-subarray-that-contains-the-largest-sum-subject-to-a-constraint-that-the-sum-is-less-than-k
int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
int row = matrix.size();
int col = matrix[0].size();
int re=INT_MIN;
for (int l=0;l<col;++l){
vector<int> sum(row,0);
for (int r=l;r<col;++r){
for (int i=0;i<row;++i){
sum[i]+=matrix[i][r];
}
int curSum = 0, m_maxSum = INT_MIN;
set<int> curSet;
curSet.insert(0);
for (int s:sum){
curSum += s;
auto it = curSet.lower_bound(curSum-k);
if (it!=curSet.end()) m_maxSum=max(m_maxSum,curSum-*it);
curSet.insert(curSum);
}
re = max(re,m_maxSum);
}
}
return re;
}
33&81 Search in Rotated Sorted Array(1,2)
题意:一个数组有移位,找出给定目标值的下标,不存在返回-1
思路:二分查找,多判断一次中间元素是否大于队头(或队尾),大于队头(队尾),说明中间往左有序,否则中间往右有序。但是注意二分边界。有重复的数字情况稍有不同,需要判断中间元素和末尾元素是否相等。
int search(vector<int>& nums, int target) {
if (nums.size()==0) return -1;
int low = 0, high = nums.size()-1;
while (low <= high){
int mid = low + (high - low)/2;
if (nums[mid] == target) return mid;
if (nums[mid]>nums[high]){
if (target<nums[mid] && target>=nums[low]){
high = mid - 1;
}else low = mid + 1;
}else{
if (target>nums[mid] && target <= nums[high]){
low = mid +1;
}else{
high = mid - 1;
}
}
}
return -1;
}
bool search(vector<int>& nums, int target) {
int low = 0;
int high = nums.size()-1;
while (low <= high){
int mid = low + ((high-low)>>1);
if (nums[mid]==target) return true;
if (nums[mid]>nums[high]){
if (nums[mid]>target && target>=nums[low]){
high = mid - 1;
}else low = mid + 1;
}else if (nums[mid]<nums[high]){
if (target>nums[mid] && target<=nums[high])
low = mid+1;
else
high = mid - 1;
}else
high--;
}
return false;
}
287 Find the Duplicate Number
题意:n+1个数,含1~n,肯定至少有一个数字是重复的,找出重复数字。
思路:此题有两种要求,一种可以改变数组,采用数组下标寻地址法存,1就存在位置0,,2存在位置1,n就存在n-1位置,这样在n位置上的数一定是多出来的。
第二种不能改变数组,那就采用快慢双指针法。
做法一:
int findDuplicate(vector<int>& nums) {
for (int i=0;i<nums.size();++i){
while (nums[i]!= nums[nums[i]-1] && nums[i]-1!=i){
swap(nums[i],nums[nums[i]-1]);
}
}
return nums[nums.size()-1];
}
做法二:
int findDuplicate(vector<int>& nums) {
int slow = nums[0];
int fast = nums[nums[0]];
while (slow!=fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
slow = 0;
while (slow != fast){
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
283 Move Zeroes
题意:将一个数组中的所有0都移到最后
题目不难,我的思路是将非零数与最前面的0进行交换,每一次交换前都需要遍历寻找最前面的零的位置,复杂度特别高。好的思路:设置一个插入下标,把非零值存入原数组,最后再补上所有零。
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0) return;
int insertPos = 0;
for (int num: nums) {
if (num != 0) nums[insertPos++] = num;
}
while (insertPos < nums.length) {
nums[insertPos++] = 0;
}
}
268 Missing Number
题意:0~n一共n+1个数,现在缺少了一个,找出数组中缺少的数
我的做法是通过补充一个数(n+1),每个数都放到它该放的地方,然后result记录n+1这个数被交换到数组中的哪一个位置,来判断缺少哪个数。
int missingNumber(vector<int>& nums) {
int ret = 0;
int n = nums.size();
nums.push_back(n+1);
for (int i=0;i<=n;++i){
while (nums[i]!=n+1 && nums[i]!=i){
swap(nums[i],nums[nums[i]]);
}
if (nums[i]==n+1) ret = i;
}
return ret;
}
最佳答案的思路是异或,因为a xor b xor a = b,所以可以通过异或抵消掉出现两次的
public int missingNumber(int[] nums) {
int xor = 0, i = 0;
for (i = 0; i < nums.length; i++) {
xor = xor ^ i ^ nums[i];
}
return xor ^ i;
}
238 Product of Array Except Self
题意:除自己之外的乘积,不能用除法,不用额外空间
思路:左右两趟遍历,巧妙
vector<int> productExceptSelf(vector<int>& nums) {
vector<int> ret(nums.size(),0);
ret[0] = 1;
int n = nums.size();
for (int i=1;i<n;++i){
ret[i] = ret[i-1]*nums[i-1];
}
int right = 1;
for (int i=n-1;i>=0;i--){
ret[i] *= right;
right *= nums[i];
}
return ret;
}
229 Majority Element II
题意:找出出现次数超过n/3次的数,提示:这样的数最多有两个
思路:类似找超过一半次数的数,维护两个变量,分别存候选数,另外两个cnt计数器,保存候选数出现的次数。类似的如果找超过n/4的数就维护三个变量。
vector<int> majorityElement(vector<int>& nums) {
int can1,can2,cnt1(0),cnt2(0);
for (int num:nums){
if (num==can1) cnt1++;
else if (num==can2) cnt2++;
else if (cnt1==0) {can1=num;cnt1=1;}
else if (cnt2==0) {can2=num;cnt2=1;}
else{
cnt1--;cnt2--;
}
}
cnt1=cnt2=0;
for (int num:nums){
if (num==can1) cnt1++;
else if(num==can2) cnt2++;
}
vector<int> re;
if (cnt1>(nums.size()/3)) re.push_back(can1);
if (cnt2>(nums.size()/3)) re.push_back(can2);
return re;
}
209 Minimum Size Subarray Sum
题意:给一个数组,求数组中最小长度的子数组的和大于等于给定数s
思路:我的做法是纪录sum数组,子数组的和就用sum数组减,然后gap从0开始遍历。但这样做的最差情况复杂度有
O(n2)
了。
正确思路:两个指针i,j,i指向子数组头,j指向子数组的尾,j先移动到子数组的和大于s,然后再移动i,并纪录和大于s时的最小数组长度
public int minSubArrayLen(int s, int[] a) {
if (a == null || a.length == 0)
return 0;
int i = 0, j = 0, sum = 0, min = Integer.MAX_VALUE;
while (j < a.length) {
sum += a[j++];
while (sum >= s) {
min = Math.min(min, j - i);
sum -= a[i++];
}
}
return min == Integer.MAX_VALUE ? 0 : min;
}
162 Find Peak Element
题意:找到一个数组中的局部极大值点,相邻元素不相等
思路:二分查找,比较mid指向的和mid+1指向的大小,如果A[mid]>A[mid+1],那么high = mid,否则low=mid+1
int findPeakElement(vector<int>& nums) {
int n = nums.size();
int low = 0;
int high = n - 1;
while (low < high){
int mid = low + ((high-low)>>1);
if (nums[mid] < nums[mid+1]){
low = mid + 1;
}else{
high = mid;
}
}
return low;
}
Maximum Product Subarray
题意:乘积最大的子数组
思路:再做又错了,保存最大最小值,有两个地方没有注意:1,初始化curmax,curmin,maxP用nums[0]初始化,可以避免只有一个元素的情况出错。2,max函数求最大时要将curmax*nums[i]与nums[i]本身进行比较,这点很重要!
int maxProduct(vector<int>& nums) {
int curmax = nums[0];
int curmin = nums[0];
int ret = nums[0];
for (int i = 1; i < nums.size(); ++i){
if (nums[i] > 0){
curmax = max(nums[i],curmax*nums[i]);
curmin = min(nums[i],curmin*nums[i]);
}else{
int tmp = curmax;
curmax = max(nums[i],curmin*nums[i]);
curmin = min(nums[i],tmp*nums[i]);
}
if (curmax > ret) ret = curmax;
}
return ret;
}
82 Remove Duplicates from Sorted List II
题意:删除单链表中的重复节点,要求只要重复的就删除
这题还是比较难的,简直offer上也有这个题,但是那个做法太过于复杂。比较好的思路是用一个多余节点放在head前,然后维护一个pre指针,pre指向的是确定不重复的一个节点,pre->next指向下一个可能重复也可能不重复的节点,必须判断下一个节点确实和下下个节点不重复,才可以把pre向前移动一位。
ListNode* deleteDuplicates(ListNode* head) {
ListNode dummy(0);
ListNode* pre = &dummy;
pre->next = head;
ListNode* cur = head;
while (cur){
while (cur->next && cur->next->val == cur->val){
cur = cur -> next;
}
//cur == pre->next说明没有进入上面的循环
if (cur == pre->next) pre = pre -> next;
else{
pre -> next = cur -> next;
}
cur = cur -> next;
}
return dummy.next;
}
对比两题,下面的算法是针对只删除重复的节点,保留第一个。思路都是相似的,不过这里找到第一个不重复的节点就可把pre指向它并且向前移动一位。
ListNode* deleteDuplicates(ListNode* head) {
ListNode* cur = head;
ListNode* pre = head;
while (cur){
while (cur->next && cur->val == cur->next->val){
cur = cur->next;
}
pre->next = cur->next;
pre = pre->next;
cur = cur -> next;
}
return head;
}
84 Largest Rectangle in Histogram
思路:
O(n)
做法是用栈保存各个bar的index,注意压栈的时候要保证压入的index对应的bar的高度一定要大于等于栈内所有index对应的bar的高度,这样可以保证在以栈顶元素为高计算面积时,向右的高度都是大于等于当前栈顶元素的高度的。
int largestRectangleArea(vector<int>& height) {
int n = height.size();
if (n==0) return 0;
height.push_back(0);
stack<int> st;
//st.push(height[0]);
int maxS = 0;
int i = 0;
while (i<=n){
if (st.empty() || height[i] >= height[st.top()]){
st.push(i++);
}else{
int h = height[st.top()];
st.pop();
int j = st.empty()? -1:st.top();
maxS = max(maxS,h*(i-j-1));
}
}
return maxS;
}
更新:谷歌面试没有过,还是编程能力不够强,需要继续努力!
336. Palindrome Pairs
题意:Given a list of unique words. Find all pairs of distinct indices (i, j) in the given list, so that the concatenation of the two words, i.e. words[i] + words[j] is a palindrome.
Example 1:
Given words = [“bat”, “tab”, “cat”]
Return [[0, 1], [1, 0]]
The palindromes are [“battab”, “tabbat”]
Example 2:
Given words = [“abcd”, “dcba”, “lls”, “s”, “sssll”]
Return [[0, 1], [1, 0], [3, 2], [2, 4]]
The palindromes are [“dcbaabcd”, “abcddcba”, “slls”, “llssssll”]
思路:乍看挺难的,好像没有解决的好办法,仔细分析可以发现其实回文可以拆分,分成左边和右边,如果左边已经是回文子串,那么只需要在左边拼接上右边子串的翻转就可以拼成一个回文串。比如abcd拆分成”a” and “bcd”,a是回文的,所以只要在a前面拼上”dcb”就可以构成”dcbabcd”了。
基于这个思路,用map存所有字符串,然后对每一个字符串进行拆分,然后在map中寻找是否存在子串的翻转串即可。
java代码:
public List<List<Integer>> palindromePairs(String[] words) {
List<List<Integer>> result = new ArrayList<>();
if (words == null || words.length<2) return result;
Map<String,Integer> wordMap = new HashMap<String, Integer>();
for (int i=0;i<words.length;i++){
wordMap.put(words[i], i);
}
for (int i=0;i<words.length;i++){
for (int j=0; j<=words[i].length(); ++j){
String pre = words[i].substring(0,j);
String pos = words[i].substring(j);
if (isPalindrome(pre)){
String toMatch = new StringBuilder(pos).reverse().toString();
if (wordMap.containsKey(toMatch) && wordMap.get(toMatch)!= i){
List<Integer> list = new ArrayList<Integer>();
list.add(wordMap.get(toMatch));
list.add(i);
result.add(list);
}
}
if (isPalindrome(pos)){
String toMatch = new StringBuilder(pre).reverse().toString();
if (wordMap.containsKey(toMatch) && wordMap.get(toMatch)!= i && pos.length()!=0){
List<Integer> list = new ArrayList<Integer>();
list.add(i);
list.add(wordMap.get(toMatch));
result.add(list);
}
}
}
}
return result;
}
public boolean isPalindrome(String str){
int st=0,end=str.length()-1;
while (st<end){
if (str.charAt(st++)!=str.charAt(end--)) return false;
}
return true;
}
c++代码
vector<vector<int>> palindromePairs(vector<string>& words) {
unordered_map<string, int> dict;
vector<vector<int>> ans;
// build dictionary
for(int i = 0; i < words.size(); i++) {
string key = words[i];
reverse(key.begin(), key.end());
dict[key] = i;
}
// edge case: if empty string "" exists, find all palindromes to become pairs ("", self)
if(dict.find("")!=dict.end()){
for(int i = 0; i < words.size(); i++){
if(i == dict[""]) continue;
if(isPalindrome(words[i])) ans.push_back({dict[""], i}); // 1) if self is palindrome, here ans covers concatenate("", self)
}
}
for(int i = 0; i < words.size(); i++) {
for(int j = 0; j < words[i].size(); j++) {
string left = words[i].substr(0, j);
string right = words[i].substr(j, words[i].size() - j);
if(dict.find(left) != dict.end() && isPalindrome(right) && dict[left] != i) {
ans.push_back({i, dict[left]}); // 2) when j = 0, left = "", right = self, so here covers concatenate(self, "")
}
if(dict.find(right) != dict.end() && isPalindrome(left) && dict[right] != i) {
ans.push_back({dict[right], i});
}
}
}
return ans;
}
bool isPalindrome(string str){
int i = 0;
int j = str.size() - 1;
while(i < j) {
if(str[i++] != str[j--]) return false;
}
return true;
}
214 Shortest Palindrome
题意:给定一个字符串,求出在前面补最少的字母使之构成回文
思路:翻转字符串然后拼到原串的后面,利用kmp求出翻转串的末几位和原串的头几位匹配,这一部分是一定会构成回文的(因为翻转后还能和原串匹配上),所以只需要在原串的头部补上翻转串没有匹配上的头几位就可以了(因为翻转串没匹配上的头几位就是原串没匹配上的末位翻转的结果)
string shortestPalindrome(string s) {
string rev_s = s;
reverse(rev_s.begin(),rev_s.end());
string str = s + '#' + rev_s;
vector<int> next(str.size()+1,0);
next[0] = -1;
for (int i = 1; i <= str.size(); ++i){
int q = next[i-1];
while (q != -1 && str[i-1]!=str[q])
q = next[q];
next[i] = q + 1;
}
int k = s.size() - next[str.size()];
return rev_s.substr(0,k)+s;
}