LintCode 解题记录 17.6.19~17.6.25

大概有十几天没有做题了,主要是出差大概耽误了一周左右的时间,进度又跟不上了。

17.6.19 晚

LintCode Next PermutationsII

给定一个序列,求其下一个序列。比如123下一个序列就是132,如果已经是最大的序列321,那么会回到最小的123。
思路:从序列的末尾开始看起,如果对于位置i的数,从i+1~n-1的数全部小于位置i的数,那么说明从位置i开始的数就是最大的。如果发现一个不是,就和位置i的数交换,然后再把位置i+1~n-1的数重新排序一下。
可能我说的比较抽象,贴一个带图的博客:http://www.cnblogs.com/grandyang/p/4428207.html
代码:

    void nextPermutation(vector<int> &nums) {
        // write your code here
        int n = nums.size(), idx = 0;
        for (int i = n-1; i >= 0; i--) {
            for (int j = n-1; j > i; j--) {
                if (nums[i] >= nums[j]) continue;
                swap(nums[i], nums[j]);
                sort(nums.begin()+i+1, nums.end());
                return;
            }
        }
        sort(nums.begin(), nums.end());//若序列是降序排列的,那么其下一个序列就是初始的最小序列。
    }

LintCode Number of Airplanes in the Sky

抽象成数学语言就是,给定一些interval,求其overlap最多的个数。
一开始想到的就是用hash来做,对于[start, end]内的所有数其hash++,最后找到最大的那个数。但是超时了,因为其实对于一个Interval来说,只有start和end两端有意义,中间的数并没有多少的意义,所以上述做法其实是做了大量的无用功。
经百度后,发现用 扫描线做法。就是把所有start,end这些时间点按照时间先后排序,同时要标记其是start还是end。最后线性遍历所有时间,如果是start就计数器就++,否则就–,找出这个过程中的最大值。
代码:

 bool cmp(pair<int, int> p1, pair<int, int> p2) {
     if (p1.first != p2.first) return p1.first < p2.first;
     return p1.second < p2.second;//意思是如果时间点相同,那么要优先下降。
 }
class Solution {
public:
    /**
     * @param intervals: An interval array
     * @return: Count of airplanes are in the sky.
     */
    int countOfAirplanes(vector<Interval> &airplanes) {
        // write your code here
        vector<pair<int, int>> v;
        for (auto interval: airplanes) {
            v.push_back(make_pair(interval.start, 1));
            v.push_back(make_pair(interval.end, 0));
        }
        sort(v.begin(), v.end(), cmp);
        int ret = 0, cnt = 0;
        for (auto vv : v) {
            vv.second == 1 ? cnt++ : cnt--;
            ret = max(ret, cnt);
        }
        return ret;
    }
};

LintCode Reorder array to construct the minimum number

很相似的题目,在PAT中应该遇到过。对于两个string A和B,如果A+B < B+A,那么就 认为 A < B。
排序之后遍历一遍把所有的string加起来。注意处理前导0的情况。

bool cmp(string A, string B) {
    string C = A+B;
    string D = B+A;
    return C < D;
}


string int2str(int num) {
    string ret = "";
    if (num == 0) ret = "0";
    while (num) {
        ret += (num%10 + '0');
        num /= 10;
    }
    reverse(ret.begin(), ret.end());
    return ret;

}
class Solution {
public:
    /**
     * @param nums n non-negative integer array
     * @return a string
     */
    string minNumber(vector<int>& nums) {
        // Write your code here
        string ret = "";
        int len = nums.size();
        vector<string> v;
        for (auto n: nums) {
            v.push_back(int2str(n));
        }
        sort(v.begin(), v.end(), cmp);
        for (auto vv: v) {
            ret += vv;
        }
        //处理前导0
        for (int i = 0; i < ret.size(); i++) {
            if (ret[i] == '0' && i != ret.size()-1) continue;
            ret = ret.substr(i);
            break;
        }
        return ret;
    }
};

6.20晚 战绩1/4

LintCode Reverse Pairs

给定一个数组A,如果i < j 且 A[i] > A[j],那么称(A[i], A[j])是一对翻转对。找出所有翻转对的个数。
思路:brute-force以O(n^2)的成绩超时。那么就需要优化,常见的思路就是按照O(n^2)->O(nlogn)->O(n)->O(logn)这样的思路来进行优化。

思路1:O(nlogn)

我们另开一个数组,从尾部到头遍历数组A,每遍历到一个位置i时,就把该数i利用二分搜索法插到新的数组中。这样插入之后该数组左边的数的个数就是翻转对的个数。
注意: 需要考虑重复元素的情况,所以插入该数时要始终把其插入最左边才能不考虑重复元素。
代码:

    long long reversePairs(vector<int>& A) {
        // Write your code here
        vector<int> v;
        long long ret = 0;
        if (A.size() == 0) return ret;
        for (int i = A.size()-1; i >= 0; i--) {
            int left = 0, right = v.size();
            while (left < right) {
                int mid = (left+right)>>1;
                if (A[i] > v[mid]) left = mid+1;
                else right = mid; //当A[i]<= mid时,right=mid,这样可以保证遇到相同元素往最左边进行寻找
            }
            v.insert(v.begin()+right, A[i]);
            ret += right;//该数的插入下标为index,其左边0~right-1共right个数就是right个翻转对
        }
        return ret;
    }
思路2:来自九章的Merge Sort思想

MergeSort的思想就是递归的把左半部分MergeSort,然后把右半部分MergeSort,最后把两段有序的数组Merge起来。由于左右部分已经有序,所以若左边部分位置i的值大于右边部分右边j的值,那么左边部分i以后的值也都大于右边部分j的值,所以翻转对的个数就是左半部分中位置i以及位置i右边的数的个数。然后这部分已经求解过了,便将其排序使其有序参与更大部分的求解。

    long long reversePairs(vector<int>& A) {
        // Write your code here
        int n = A.size();
        tmp = new int[n];
        return mergeSort(A, 0, n-1);
    }
    long long merge(vector<int> &A, int l, int m, int r) {
        int i = l, j = m + 1, k = l;
        long long ans = 0;
        while (i <= m && j <= r) {
            if (A[i] > A[j]) {
                tmp[k++] = A[j++];
                ans += m - i + 1;
            } else {
                tmp[k++] = A[i++];
            }
        }
        while (i <= m) tmp[k++] = A[i++];
        while (j <= r) tmp[k++] = A[j++];
        for (i = l;i <= r; ++i)
            A[i] = tmp[i];
        return ans;
    }
    long long mergeSort(vector<int> &A, int l,int r) {
        long long ans = 0;
        if (l < r) {
            int m = (l + r) >> 1;
            ans += mergeSort(A, l, m);
            ans += mergeSort(A, m + 1, r);
            ans += merge(A, l, m, r);
        }
        return ans;
  }

LintCode Search for a Range

给定一个有序序列和一个目标数target,让你找出这个序列中等于target数的区间(即有重复的数)。找不到target则返回-1。
要求时间复杂度为O(logn)。
思路:典型的二分搜索的题目。思路就是用两次二分查找,一次查找区间的左边界,一次查找区间的右边界即可。
代码:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res(2, -1);
        int left = 0, right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        if (nums[right] != target) return res;
        res[0] = right;
        right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) left = mid + 1;
            else right= mid;
        }
        res[1] = left - 1;
        return res;
    }
};

被二分法很多的细节地方都弄晕了,比如加不加等号,考不考虑相等的情况,边界怎么写等等很多细节,刚好发现一篇很好的博客:http://www.cnblogs.com/grandyang/p/6854825.html
因为懒得注册所以没有评论,但还是感谢上面博主的指点!
常见的二分查找主要分为如下3种情况:
1.查找给定的数,找不到则返回-1。

int find(vector<int>& nums, int target) {
    if (nums.size() == 0) return -1;
    int left = 0, right = nums.size()-1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) left = mid + 1;
        else right = mid+1;
    }
    return -1;
}

2.查找序列中第一个不小于目标值的数,返回其下标。
这种情形又可分为两种情形:1.序列中没有目标值 2.序列中有多个目标值。显然这种情形下nums[mid] == target这条语句就没有多少作用了,因为就算判断出了nums[mid] == target也得不到答案。

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();//这里right不能声明成nums.size()-1;
    while (left < right) {//left == right时跳出循环
        int mid = (left + right) >> 1;
        if (nums[mid] < target) left = mid + 1;
        else right = mid;//right所指的值永远是不小于目标值的
    }
    return right;
}

那么right-1就是第一个小于目标值的下标。
3.查找序列中第一个大于目标值的数,返回其下标。
思路和2差不多,只需要改变一个符号,就能改变搜索方向。

int find(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) left = mid + 1; //把<改为<=
        else right = mid;
    }
    return right;
}

那么right-1就是最后一个小于等于目标值的数了。

关于这道题,我还发现了相同思路的另外一种写法,区别就是二分法的细节不同,所以也来贴一下。

public int[] searchRange(int[] A, int target) {  
    int[] res = {-1,-1};  
    if(A==null || A.length==0)  {  
        return res;  
    }  
    int ll = 0;  
    int lr = A.length-1;  
    while(ll<=lr)  {  
        int m = (ll+lr)/2;  
        if(A[m]<target)  {  
            ll = m+1;  
        } else {  
            lr = m-1;  
        }  
    }  
    int rl = 0;  
    int rr = A.length-1;  
    while(rl<=rr)  {  
        int m = (rl+rr)/2;  
        if(A[m]<=target)  {  
            rl = m+1;  
        }  
        else {  
            rr = m-1;  
        }  
    }  
    if(ll<=rr)  {  
        res[0] = ll;  
        res[1] = rr;  
    }  
    return res;  
} 

LintCode Search in Rotate Array I

给定一个旋转过的有序序列,请用O(logn)的方法找到target。
思路: 二分搜索法。由于平时我们的二分搜索都是在有序序列上的,虽然这道题给的序列并不有序,但是它的一部分是有序的。所以我们可以在有序的子序列上进行二分查找。
先贴代码:

    int search(vector<int> &A, int target) {
        // write your code here
        int n = A.size();
        if (n == 0) return -1;
        int l = 0, h = n-1;
        while (l <= h) {
            int mid = (l+h) >> 1;
            if (A[mid] == target) return mid;
            if (A[l] < A[mid]) { //说明mid的左半部分都是有序的
                if (A[l] <= target && A[mid] > target) h = mid-1;//如果target在有序的部分上
                else l = mid+1; //否则就从右边开始找
            } else {//同理,说明右半部分是有序的
                if (A[mid] < target && A[h] >= target) l = mid+1;
                else h = mid-1;
            }
        }
        return -1;
    }

LintCode Search in Rotate Array II

和上一题不同的是,本题的元素可以重复。那么会带来什么影响呢?也就是在判断哪部分有序的时候出现了A[l] == A[mid]的情况,这种情况下无法判断哪部分是有序的。但稍微改变一下就能解决这个问题。当A[l] == A[mid]时,我只要l++即可(即丢弃这个元素,因为这个元素并不等于target,也不会改变原有的顺序信息,所以丢弃掉)。

    bool search(vector<int> &A, int target) {
        // write your code here
                int n = A.size();
        if (n == 0) return false;
        int l = 0, h = n-1;
        while (l <= h) {
            int mid = (l+h) >> 1;
            if (A[mid] == target) return true;
            if (A[l] < A[mid]) {
                if (A[l] <= target && A[mid] > target) h = mid-1;
                else l = mid+1;
            } else if (A[l] > A[mid]) {
                if (A[mid] < target && A[h] >= target) l = mid+1;
                else h = mid-1;
            } else {
                l++;
            }
        }
        return false;
    }

6.22晚

LintCode Sort Colors

给定一个序列分别由0, 1, 2组成,要求把这个序列排序,按照0,1,2的顺序。
不能使用库sort。
思路1:二次遍历,统计0,1,2的个数,然后在按照个数复写该序列。
思路2:一次遍历,同时用两个指针分别指向开头和末尾,遍历到0时,就和头指针所指元素交换,然后头指针+1,遍历到2时,就和尾元素交换,然后尾元素-1。注意和尾元素交换过来的元素还需要继续判断,而和头元素交换则不用判断。

    void sortColors(vector<int> &nums) {
        // write your code here
        if (nums.size() == 0) return;
        int zero, one, two;
        zero = one = 0, two = nums.size()-1;
        while (one <= two) {
            if (nums[one] == 1) {
                one++;
            }
            else if (nums[one] == 0) {
                swap(nums[one++], nums[zero++]);
            }
            else if (nums[one] == 2) {
                swap(nums[one], nums[two--]);
            }
        }
    }

总结:自己也一直在往遍历然后交换元素上去想,可总是差点意思。或者说想的复杂了一点,导致没能做出来。

LintCode Spiral Matrix

很经典的题目了,绕圈输出矩阵。考察基本功吧,注意细节即可。

    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        // Write your code here
        vector<int> ret;
        if (matrix.size() == 0 || matrix[0].size() == 0) return ret;
        int up = 0, down = matrix.size()-1, left = 0, right = matrix[0].size()-1;
        while (up <= down && left <= right) {
            for (int i = left; i <= right; i++) {
                ret.push_back(matrix[up][i]);
            }
            for (int i = up+1; i <= down; i++) {
                ret.push_back(matrix[i][right]);
            }
            //下面的判断条件要多一个,防止重复输出,比如只有一行的情形
            for (int i = right-1; i >= left && up != down; i--) {
                ret.push_back(matrix[down][i]);
            }
            for (int i = down-1; i > up && left != right; i--) {
                ret.push_back(matrix[i][left]);
            }
            up++,down--,left++,right--;
        }
        return ret;
    }

LintCode Spiral Matrix II

和1差不多,不过比1好像还简单一点。

    vector<vector<int>> generateMatrix(int n) {
        // Write your code here
        vector<vector<int>> ret(n, vector<int>(n, 0));
        int cnt = 1;
        int up = 0, down = n-1, left = 0, right = n-1;
        while (cnt <= n*n) {
            for (int i = left; i <= right; i++) {
                ret[up][i] = cnt++;
            }
            for (int i = up+1; i <= down; i++) {
                ret[i][right] = cnt++;
            }
            for (int i = right-1; i >= left; i--) {
                ret[down][i] = cnt++;
            }
            for (int i = down-1; i > up; i--) {
                ret[i][left] = cnt++;
            }
            up++, down--, left++, right--;
        }
        return ret;
    }

LintCode The Smallest Difference

这题简单,排序,然后类似MergeSort的用两个指针来判断。

    int smallestDifference(vector<int> &A, vector<int> &B) {
        // write your code here
        sort(A.begin(), A.end());
        sort(B.begin(), B.end());
        int pa = 0, pb = 0;
        int ret = INT_MAX;
        while (pa < A.size() && pb < B.size()) {
            ret = min(ret, abs(A[pa] - B[pb]));
            if (A[pa] > B[pb]) pb++;
            else pa++;
        }
        return ret;
    }

LintCode Trapping Rain Water

乍看没有啥思路,给的提示是用Forward and Backward Traversal 和 Two Pointers来做的。自己画图体验一下就发现了一个point: 用left和right来遍历该数组。left保持不动,向右移动right,如果发现一个heights[right] >= heights[left],那么无论right右边时怎么样,从left到right这一部分的盛水体积都是固定的。那么如果不存在满足条件的right呢?那么便只需要再从右边利用同样遍历原理即可。
先上自己AC的代码:

    int trapRainWater(vector<int> &heights) {
        // write your code here
        int n = heights.size();
        if (n <= 2) return 0;
        int ret = 0, tmp = 0;
        int left = 0, right = 0;
        while (right < n) {
            //while (left < n-1 && heights[left] <= heights[left+1]) left++;
            right = left+1;
            for (; right < n && heights[right] < heights[left]; right++) {
                tmp += heights[left] - heights[right];
            }
            if (right < n && heights[right] >= heights[left]) {
                ret += tmp;
                tmp = 0;
                left = right;
            }
        }
        right = n-1;
        tmp = 0;
        while (right > left) {
            //while (right > left && heights[right-1] >= heights[right]) right--;
            int l = right - 1;
            for (; l >= left && heights[l] < heights[right]; l--) {
                tmp += heights[right] - heights[l];
            }
            if (l >= left && heights[l] >= heights[right]) {
                ret += tmp;
                tmp = 0;
                right = l;
            }
        }
        return ret;
    }

嗯。。和别人的比起来果然一如既往的又臭又长。。。不说了,看一下网上大神们的优秀解答吧!
http://www.cnblogs.com/felixfang/p/3713197.html
http://blog.csdn.net/linhuanmars/article/details/20888505
原谅我懒了。。由于后天就回家了,所以暂时也不想写了。回家之后接着把题刷起来就好了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值