Categories
不学无术

LeetCode 328. Odd Even Linked List

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.
You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.
Example:
Given 1->2->3->4->5->NULL,
return 1->3->5->2->4->NULL.
Note:
The relative order inside both the even and odd groups should remain as it was in the input.
The first node is considered odd, the second node even and so on …

思路

既然是要O(n)时间复杂度,并且O(1)空间复杂度,基本就能想到要用俩指针了。其中一个指针指向当前的奇数位元素,另一个指向偶数位的。
LeetCode328
大概就是这个意思啦,然后指针不断往前挪动。注意要保存偶数指针的头,因为奇数结束之后要串上偶数的~

代码

里面的oddTail是为了防止链表长度为奇数的时候,里面currOdd指针走到NULL的情况。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head == NULL || head->next == NULL)
            return head;
        ListNode* currOdd = head;
        ListNode* currEven = head->next;
        ListNode* evenHead = currEven;
        ListNode* oddTail = currOdd;
        while(currOdd != NULL && currEven != NULL){
            currOdd->next =currEven->next;
            if(currOdd->next != NULL)
                currEven->next = currOdd->next->next;
            oddTail = currOdd;
            currOdd = currOdd->next;
            currEven = currEven->next;
        }
        if(currOdd != NULL)
            currOdd->next = evenHead;
        else
            oddTail->next = evenHead;
        return head;
    }
};

 

Categories
不学无术

Longest Common Subsequence 最长公共子序列

这是个很老的问题了,最近总是碰到,大三时候学的算法好多都忘记了,不如重新学一遍。
比起XXblog上的教程,我最喜欢的还是这个版本:http://www.ics.uci.edu/~eppstein/161/960229.html
里面的代码干净利落,解释也非常的清晰,一步一步怎么来的都很好。
这个问题是个动态规划的问题,最基本的样子是这样的:

假设当前搜索到字符串A,B中的字符a[i],b[j].
LCS[i,j]表示字串A,B分别在位置[i][j]及以后的最大子序列数量
IF A[i] == B[j]:
    LCS[i,j] = 1 + LCS[i+1][j+1] # 当前的算入,然后A,B再往后挪一格
ELSE
    LCS[i,j] = max(LCS[i][j+1], LCS[i+1][j]) # 分别挪动A,B后一格看看哪个好
ENDIF

由于这样递归能弄出的子问题里面,好多是重复的,因此我们可以像计算斐波那契数列那样,采用自底向上的方法,用非递归的方式实现。即从字符的末尾往前推导LCS[i][j],这样当计算LCS[i][j]的时候, LCS[i+1][j]或LCS[i][j+1]已经计算好了。
具体还是看英文教程吧,这里给出C++实现的代码。因为下午例会还得讲PPT,下面的代码用了全局变量传递计算好的序列,有点丑。

#include <iostream>
using namespace std;
/**
 * Longest Common Subsequence
 */
int** storeMat;
int max(int x, int y){
    return x>y? x:y;
}
int getLCSLength(char* A, char* B){
    int sz_A = strlen(A);
    int sz_B = strlen(B);
    storeMat = new int*[sz_A+1];
    for(int i=0; i<=sz_A; i++){
        storeMat[i] = new int[sz_B+1];  // Allocate memory
    }
    for(int i=sz_A; i>=0; i--){
        for(int j=sz_B; j>=0; j--){
            if(A[i] == '\0' || B[j] == '\0'){
                storeMat[i][j] = 0;
            } else if (A[i] == B[j]) {
                storeMat[i][j] = 1 + storeMat[1+i][1+j];
            } else {
                storeMat[i][j] = max(storeMat[1+i][j], storeMat[i][1+j]);
            }
        }
    }
    return storeMat[0][0];
}
string getLCS(char* A, char* B){
    if(NULL == storeMat){
        getLCSLength(A, B);
    }
    int i=0, j=0;
    string result;
    int sz_A = strlen(A); int sz_B = strlen(B);
    while(i < sz_A && j<sz_B){
        if(A[i] == B[j]){
            result += A[i];
            i++;
            j++;
        } else if (storeMat[i+1][j] > storeMat[i][j+1]){
            i++;
        } else {
            j++;
        }
    }
    return result;
}
int main() {
    char a[50], b[50];
    scanf("%s %s", a, b);
    cout << getLCS(a, b);
    return 0;
}

 

Categories
不学无术

LeetCode 239. Sliding Window Maximum

题目

Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
For example,
Given nums = [1,3,-1,-3,5,3,6,7], and k = 3.

Window position                Max
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

Therefore, return the max sliding window as [3,3,5,5,6,7].

思路

又是《剑指Offer》的原题(#65),大致讲一下思路吧。
思路来源于之前做过的一道题,是要求设计一个栈,可以用O(1)时间找到栈中最大(最小)元素的。当时的方法是,另辟一个栈,记录可能会改变最大值的那些值(可以称他们为关键值),当这弹出的栈顶元素恰好是这些值的时候,把关键值栈顶的元素也弹出。这就相当于做了“撤销”操作,这时关键值栈顶的新元素也就是目前的最大值了。
假设我们用一个容器存放那些可能成为最大值的元素,那维护它过程就发生在向后移动窗口读入一个新元素a[i]时,主要的想法有两步:

  • 如果a[i]比容器中那些排在它前面的(先前读入的)元素大,由于a[i]的存活时间肯定比老一辈们来的长,因此前面那些比它的值还小的元素就可以删去了;
  • 删去那些“过气”的元素,也就是目前已经不在窗口中的那些元素;

每挪动一下,可以得知当前窗口最大的元素肯定就是在容器最前头的(可能是还没“过气”老大),后面排着一串老大挂掉后想当老大的元素
由于按照时间顺序,“过气”元素在容器的开头,而我们又要从容器的末尾开始找那些比a[i]小的元素,因此有这么两个重要决定:

  1. 使用双端队列存放候选元素:因为我们要方便地操作数组的首末项;
  2. 队列中存放元素的序号而不是值:这是因为我们要判断哪些元素已经滑出窗口了,如果存储值的话,每次还得重新到原序列里找位置;

整个方法的时间复杂度是O(n),空间复杂度应该也是O(n)

代码

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int numSz = nums.size();
        vector<int> results;
        if(numSz <= 0 || k > numSz){
            return results;
        }
        deque<int> working;
        //Init deque
        for(int i=0; i<k; i++){
            while(!working.empty() && nums[i]>=nums[working.back()]){
                working.pop_back();
            }
            working.push_back(i);
        }
        results.push_back(nums[working.front()]);
        //Interate through remaining nums
        for(int i=k; i<numSz; i++){
            while(!working.empty() && working.front() <= (i-k)){
                working.pop_front(); //Remove the out-of-window elements
            }
            while(!working.empty() && (nums[i]>=nums[working.back()])){
                working.pop_back();  //Remove the smaller elements
            }
            working.push_back(i);
            results.push_back(nums[working.front()]);
        }
        return results;
    }
};

 

Categories
不学无术

LeetCode 335. Self Crossing

题目:

You are given an array x of n positive numbers. You start at point (0,0) and moves x[0] metres to the north, then x[1] metres to the west, x[2] metres to the south,x[3] metres to the east and so on. In other words, after each move your direction changes counter-clockwise.
Write a one-pass algorithm with O(1) extra space to determine, if your path crosses itself, or not.
Example 1:

Given x = [2, 1, 1, 2],
┌───┐
│   │
└───┼──>
    │
Return true (self crossing)

Example 2:

Given x = [1, 2, 3, 4],
┌──────┐
│      │
│
│
└────────────>
Return false (not self crossing)

Example 3:

Given x = [1, 1, 1, 1],
┌───┐
│   │
└───┼>
Return true (self crossing)

思路:

https://leetcode.com/discuss/88054/java-oms-with-explanation
本人愚钝,想不到O(1)空间复杂度的方法,只能看Discussion啦,看完以后发现,遇到问题能够分析出各种情况真是不容易。
把大神的代码总结一下,可以变成下面这张图,图中给出了可能发生self-crossing的所有情况(其他情况都是这些情况的”旋转”版本):
LeetCode_Self_Crossing
所以就是遍历所有移动步x[i],判断之前的情况是否在这三种情况之内,如果有的话,那肯定就有crossing了。其中情况2的意思是,当前的步进x[i]覆盖到了x[i-4]了,其他情况看图应该都好理解的。

代码:

class Solution {
public:
    bool isSelfCrossing(vector<int>& x) {
        int numSz = x.size();
        if(numSz <= 3)
            return false;
        for(int i=3; i<numSz; i++){
            if(x[i]>=x[i-2] && x[i-1]<=x[i-3]) //Cond 1
                return true;
            else if(i>=4 && x[i-1]==x[i-3] && x[i]+x[i-4]>=x[i-2])//Cond 2
                return true;
            else if(i>=5 && x[i-1]<=x[i-3] && x[i-4]<=x[i-2] && x[i]+x[i-4]>=x[i-2] && x[i-1]+x[i-5]>=x[i-3]) //Cond 3
                return true;
        }
        return false;
    }
};

 

Categories
不学无术

LeetCode 142. Linked List Cycle II

Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
Note: Do not modify the linked list.
Follow up:
Can you solve it without using extra space?

思路

其实还有更简单的方法,在《Cracking the Coding Interview》上有提到。通过数学计算可以推导出来,用快行指针的方法判断出环后,可以不用计算环大小,把快指针变慢然后放到链头上,两个指针继续走到相碰就剩入口了。
分为三步:判断环,计算环的大小,找到入口
判断环就是老一套,用2倍速指针就行,这个之前的题目里头有;
计算环的大小k,环的大小么,刚才相碰的地方找个指针绕圈圈走。记个数,再碰到相遇地点就是环大小了;
找到入口后,我们有两个普通速度的指针pA,pB,让其中一个pA先往前走k步,然后两个一起开始挪动,可以知道,如果pA与pB又相遇了,那相遇点就是环的入口了。此时pA比pB刚好多走了一整圈。

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* meetingNode(ListNode* head){
        //Find the meeting node
        if(head == NULL)
            return NULL;
        ListNode* slow = head->next;
        if(slow == NULL)
            return NULL;
        ListNode* fast = slow->next;
        while(fast != NULL && slow != NULL){
            if (slow == fast)
                return slow;
            slow = slow->next;
            fast = fast->next;
            if(fast != NULL)
                fast = fast->next;
        }
        return NULL;
    }
    ListNode *detectCycle(ListNode *head) {
        ListNode* meet = meetingNode(head);
        if(meet == NULL)
            return NULL;
        //Get loop size
        ListNode* temp = meet->next;
        int count = 1;
        while(temp != meet){
            count++;
            temp = temp->next;
        }
        //Pick one pointer at start, move `count` steps forward
        temp = head;
        meet = head;
        while(count > 0){
            count--;
            meet = meet->next;
        }
        //Find the cycle start
        while(temp != meet){
            temp = temp->next;
            meet = meet->next;
        }
        return meet;
    }
};

 

Categories
不学无术

LeetCode 287. Find the Duplicate Number | 智商被碾压!

看了LeetCode上牛人的解答,感觉我的智商被碾压了~

原题

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
给定一个包含n+1个整数的数组nums,该数组中的每个整数都在[1,n]的范围内,假设数组中只有一个数是重复的,找到那个重复的数。
Note|提示:

  1. You must not modify the array (assume the array is read only).|    不能改动数组内容
  2. You must use only constant, O(1) extra space.|    在O(1)的空间复杂度内完成
  3. Your runtime complexity should be less than O(n2).|    时间复杂度不超过O(n^2)
  4. There is only one duplicate number in the array, but it could be repeated more than once.|    只有一个重复数字,但是可能重复超过一次

思路1:O(nlogn)时间复杂度

英文版解释:https://leetcode.com/discuss/60830/solutions-explanation-space-without-changing-input-array
这个解法涉及到鸽巢原理。
假设有n=10的一个数组(大小是n+1),那我目前的搜索范围是[1,10],先找中间的数mid=5.现在我们遍历数组的所有元素并统计<=5的元素个数,记作n_lteq5好了,那么有:

  • 如果n_lteq5>5,那就有6个数字占了本来只有5个的坑位,那目标数字肯定是在[1,5]的范围内了;
  • 如果n_lteq5<=5,那前面的元素都挺守规矩的,得看看[6,10]里头哪个数字在作怪;

这样每一次判断,问题的规模都会缩小一半,一共n+1个数字,时间复杂度是O(nlogn)
感觉碰到了很多二分思想的题目啊,关键点就是要找到一个什么划分方式把问题的规模缩小掉,而在这道题里头用到的就是鸽巢原理。

思路1代码

class Solution {
public:
    int countLtEq(const vector<int>& nums, int n){
        int count = 0;
        for(int i=0; i<nums.size(); i++){
            if(nums[i] <= n)
                count ++;
        }
        return count;
    }
    int findDuplicate(vector<int>& nums) {
        int n = nums.size() - 1;
        int min = 1, max = n;
        int mid;
        int ltEqCnt;
        while(min < max){
            mid = (min + max)/2;
            ltEqCnt = countLtEq(nums, mid);
            if(ltEqCnt <= mid){
                //[min, mid]是正常的,找另一边
                min = mid+1;
            } else {
                max = mid;
            }
        }
        return min; // min == max
    }
};

 

插曲:如果可以改变数组元素呢?

感觉在剑指Offer上遇到过类似的题目。如果可以改变数组元素的话,可以按照下面的思路进行,时间复杂度是O(n)。基本思想是,一个萝卜一个坑,把搜索到的数放到自己应该在的位置上,比如1应该放在第一个位置上,如果后面再找到一个1,我们就会发现1这个位置的坑被前一个1占了,那就冲突了。
方便起见,数组的位置按1,2,3,…描述(而非0,1,2…)。假设有数组[5,4,2,3,1,5,6] ,当前工作的位置pos=1

  • 先看第pos=1个数字5,在设想的排好序的序列里,5应该就在第5个位置上,因此我们把5和第5个位置上现有的数字1进行调换,结果就变成了[1,4,2,3,5,5,6] ;
  • 看换回来的数字1,由于已经在1号位上,这次处理就结束了,往前挪一下,pos=2,看下一个数字;
  • pos=2的数字是4,本来应该在4号位上,因此把4和4号位上的 3调换,数组变成[1,3,2,4,5,5,6] ;
  • 这时候事情没完,因为换回来的3不在应该有的3号位上,因此把3和3号位上占着的2调换,数组变成[1,2,3,4,5,5,6] ;
  • 换回的数字2是正确的位置,因此pos++,变成pos=3,类似地检查pos=4, 5发现都符合要求;
  • pos=6时,此时位置上的元素是5,于是我们想把这个5归为到5号位,但是检查5号位的时候我们发现,上面已经有一个5了,这就说明5是重复数值了,搞定

可能有问,那如果原来的数组是[5,4,2,3,1,6,5] 呢?很显然,如果前面找到pos=6的时候都没发生冲突,那罪魁祸首肯定在最后个元素上面了。
这种方法只要遍历一次数组并做相应替换就行,除了会改变数组内容外,其他条件都满足,可惜啊。

思路2:O(n)时间复杂度

大神,大家快去膜拜:http://keithschwarz.com/interesting/code/?dir=find-duplicate
据说越是经典的代码行数越是写的简单,然而你并不能看懂…
似乎是把数组中的值看做单链表的next指针的值的,还在理解中~这个算法理论上是正确的。
 

Categories
不学无术

LeetCode 330. Patching Array

题目

Given a sorted positive integer array nums and an integer n, add/patch elements to the array such that any number in range [1, n] inclusive can be formed by the sum of some elements in the array. Return the minimum number of patches required.
Example 1:
nums = [1, 3], n = 6
Return 1.
Combinations of nums are [1], [3], [1,3], which form possible sums of: 1, 3, 4.
Now if we add/patch 2 to nums, the combinations are: [1], [2], [3], [1,3], [2,3], [1,2,3].
Possible sums are 1, 2, 3, 4, 5, 6, which now covers the range [1, 6].
So we only need 1 patch.
Example 2:
nums = [1, 5, 10], n = 20
Return 2.
The two patches can be [2, 4].
Example 3:
nums = [1, 2, 2], n = 5
Return 0.

思路

说实话这道题没有想出来,还以为是个排列组合的问题,后来看了解释才恍然大悟。网上版本有很多种,我看的官网discussion里头的(这里)。
思路就是贪心法,假设目标是覆盖[0,n],当前我们的组合能够覆盖[0,miss)的数字(注意这里的miss是目前无法到达的目标,我们得用贪心的思想让这个目标可达),并且目前正在处理有序数组中num[i]的数,那么有两种情况需要考虑:

  • 如果num[i]比miss来的小,说明总有一个之前有的数,加上了num[i]以后能够到达miss完成任务。因为我们比较贪心,想让可覆盖的数越多越好,所以把num[i]带上,得到最大可以覆盖的范围[0,miss+num[i]);
  • 如果num[i]比miss来的大,那么显然把num[i]加进去是无法得到miss的。比如:目前有0,1,2,…,9可以覆盖(即miss=10),来了个num[i]=12的,显然加入12并不能解决覆盖10的问题(试想一下0+12都比10来的大)。所以最经济实惠的办法就是把miss自己加进去,这样还能把覆盖范围拓展到[0,miss*2);

代码

class Solution {
public:
    int minPatches(vector<int>& nums, int n) {
        long known_sum = 1, count = 0, i = 0;
        while(known_sum <= n){
            if(i < nums.size() && nums[i]<=known_sum){
                //If we have a choice to expand the range
                known_sum += nums[i++]; //The best choice is to add nums[i] in, which can reach up to (known+nums[i])
            } else {
                //Or we have to add in one item to make the best range, which means to double the range to 2*known_sum
                known_sum += known_sum;
                count++;
            }
        }
        return count;
    }
};

 

Categories
不学无术

LeetCode 36. Valid Sudoku

Determine if a Sudoku is valid, according to: Sudoku Puzzles – The Rules.
The Sudoku board could be partially filled, where empty cells are filled with the character '.'.

A partially filled sudoku which is valid.
Note:
A valid Sudoku board (partially filled) is not necessarily solvable. Only the filled cells need to be validated.

思路

行搜索、列搜索、sub-box搜索,根据规则来就行了

代码

class Solution {
public:
    bool isValidSudoku(vector<vector<char>>& board) {
        set<char> workingSet;
        // Row
        char c;
        for(int row = 0; row<9; row++){
            workingSet.clear();
            for(int col=0; col<9; col++){
                c = board[row][col];
                if(c == '.')
                    continue;
                if(workingSet.count(c) > 0)
                    return false;
                workingSet.insert(c);
            }
        }
        // Col
        for(int col = 0; col<9; col++){
            workingSet.clear();
            for(int row=0; row<9; row++){
                c = board[row][col];
                if(c == '.')
                    continue;
                if(workingSet.count(c) > 0)
                    return false;
                workingSet.insert(c);
            }
        }
        // Sub-box
        for(int lPos=0; lPos < 9; lPos+=3){
            for(int tPos=0; tPos<9; tPos+=3){
                workingSet.clear();
                for(int i=0; i<3; i++){
                    for(int j=0; j<3; j++){
                        c = board[lPos+i][tPos+j];
                        if(c == '.')
                            continue;
                        if(workingSet.count(c) > 0)
                            return false;
                        workingSet.insert(c);
                    }
                }
            }
        }
        return true;
    }
};

 

Categories
不学无术

LeetCode 264. Ugly Number II

Write a program to find the n-th ugly number.
Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. For example, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.
Note that 1 is typically treated as an ugly number.

思路

《剑指Offer》上的原题34,所以就说个大概吧。
下一个丑数是之前的丑数(最初是1)乘以2、3或者5得到的,所以下一个丑数就是之前的某个数乘出来的。这样的找法比从1到n一个个判断是不是丑数高效很多,不然每个数都要去除个2、3、5判断一下。
由于要管次序问题,所以安排三个指针,分别记录乘以2、3、5后不超过当前最大丑数的位置,假设是p2, p3, p5,下一次找下个丑数的时候,从(p2)2、(p3)3、(p5)5里头找到个最小的,加在数组最后面,然后更新三个指针的值,如此往复……

代码

class Solution {
public:
    int nthUglyNumber(int n) {
        if(n <= 0)
            return 0;
        int* uglyAry = new int[n];
        uglyAry[0] = 1;
        int nextPos = 1;
        int *pNextUgly2 = uglyAry;
        int *pNextUgly3 = uglyAry;
        int *pNextUgly5 = uglyAry;
        while(nextPos < n){
            int nextUgly = min3(*pNextUgly2 * 2, *pNextUgly3 * 3, *pNextUgly5 * 5);
            uglyAry[nextPos] = nextUgly;
            //Move forward to find next possible candidates
            while(*pNextUgly2 * 2 <= nextUgly)
                pNextUgly2++;
            while(*pNextUgly3 * 3 <= nextUgly)
                pNextUgly3++;
            while(*pNextUgly5 * 5 <= nextUgly)
                pNextUgly5++;
            nextPos++;
        }
        int ugly = uglyAry[n-1];
        delete []uglyAry;
        return ugly;
    }
    int min3(int n1, int n2, int n3){
        int min = n1<n2?n1:n2;
        min = min<n3?min:n3;
        return min;
    }
};

 
 

Categories
不学无术

LeetCode 257. Binary Tree Paths

Given a binary tree, return all root-to-leaf paths.
For example, given the following binary tree:

   1
 /   \
2     3
 \
  5

All root-to-leaf paths are:

["1->2->5", "1->3"]

代码

/**
 * 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:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> results;
        vector<int> path;
        binaryTreePaths(root, path, results);
        return results;
    }
    void binaryTreePaths(TreeNode* root, vector<int> path, vector<string>& results){
        if(root == NULL){
            //Error
            return;
        }
        path.push_back(root->val);
        if(root->left == NULL && root->right == NULL) {
            //Leaf
            string result;
            int pSize = path.size();
            for(int i=0; i<pSize; i++){
                result += to_string(path[i]);
                if(i != pSize - 1)
                    result += "->";
            }
            results.push_back(result);
        } else {
            if(root->left != NULL)
                binaryTreePaths(root->left, path, results);
            if(root->right != NULL)
                binaryTreePaths(root->right, path, results);
        }
    }
};