leetcode题解日练--2016.7.10

日练三题,冰冻三尺非一日之寒。

今日题目:1、在一个旋转的数组中找最小数;❤2、数的组合(k元素子集);3、不同的加括号方式;4、数的排列。

今日摘录:最初我们来到这个世界,是因为不得不来;最终我们离开这个世界,是因为不得不走。做人不能忘记四条,话不要说错,床不要睡错,门槛不要踏错,口袋不要摸错。 —《活着》

153. Find Minimum in Rotated Sorted Array | Difficulty: Medium

Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

Find the minimum element.

You may assume no duplicate exists in the array.

题意:一个已经经过移位的数组,找出其中最小的元素,假设没有重复元素的存在。
思路:
1、一个时间复杂度为O(N)的方法,遍历数组的元素,如果如果后一个元素比前一个小,那么那个就是我们要找的。

class Solution {
public:
    int findMin(vector<int>& nums) {
        for(int i=0;i<nums.size()-1;i++)
        {
            if(nums[i]>nums[i+1])   return nums[i+1];
        }
        return nums[0];
    }
};

结果:4ms
2、直接用二分查找的思想,mid=left+(right-left)/2;如果nums[mid]>nums[right],就说明待查找元素在mid位置元素的右边,left=mid+1,如果nums[mid]

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

结果:4ms

77. Combinations | Difficulty: Medium

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

For example,
If n = 4 and k = 2, a solution is:

[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

题意:给定n和k,写出所有n个数中选k个数的组合。

思路:
1、直接回溯就可以搞定

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> tmp;
        combineSub(res,tmp,n,k);
        return res;
    }

    void combineSub(vector<vector<int>> &res,vector<int>tmp,int n,int k )
    {
        if(k==0)
        {
            res.push_back(tmp);
            return;
        }
        if(k>0)
        {
        for(int i=tmp.empty()?1:tmp.back()+1;i<=n;i++)
        {
            tmp.push_back(i);
            combineSub(res,tmp,n,k-1);
            tmp.pop_back();
        }
        }

    }
};

结果:580ms
居然这么慢,应该是有什么地方没写对
稍微改进下

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> tmp(k,0);
        combineSub(res,tmp,1,n,k);
        return res;
    }

    void combineSub(vector<vector<int>> &res,vector<int>tmp,int start,int n,int k )
    {
        if(k==0)
        {
            res.push_back(tmp);
            return;
        }

        for(int i=start;i<=n-k+1;i++)
        {
            tmp[tmp.size()-k]=i;
            combineSub(res,tmp,i+1,n,k-1);
            tmp[tmp.size()-1] = 0;
        }

    }
};

结果:92ms

2、再做一点改进

class Solution {
public:
    vector<vector<int> > combine(int n, int k) {
        // dfs method
        if(k == 0 || n == 0 || n<k)
            return result;
        v.resize(k);
        combinecore(1, n, k);
        return result;
    }
    void combinecore(int start, int n, int k) {
        int i = start;
        while(i<=(n-k+1)) {
            v[v.size()-k] = i;
            i++;
            if(k>1)
                combinecore(i, n, k-1);
            else
                result.push_back(v);
        }
    }
private:
    vector<int> v;
    vector<vector<int> > result;
};

结果:84ms

3、利用k元素子集的方法去做
换个角度来看,从1-n中选择k个数字进行组合。n=4,k=2是不是某种程度上等价于
XXXX这样一个四位二进制数,其中只能某两位取1,有多少种组合相比不难得出,就是C4取2种,那么如何获得这些可能性呢?
分四步来解决:
1、求出最低位的1开始的连续的1的区间(00010111->00000111);
2、将这一区间全部变为0,并将区间左侧的那个0变为1(00010111->00011000);
3、将第1步里取出的区间右移,直到剩下的1个数减少1个(00000111->00000011);
4、将第2步和第3步的结果按位取或(00011000|00000011-> 00011011)。
伪代码如下:

    int comb=(1<<k)-1; //最小的k元素子集
    while(comb < 1<<n)
    {
        //处理每一个子集
        ……
        int x=comb & -comb; //x表示最低位1的位置
        int y=comb + x; //y表示4步中的第2步
        comb= (((comb&~y)/x )>>1)|y;
    }
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> result;
        int comb=(1<<k)-1;
        while(comb< 1<<n)
        {
            vector<int> r;
            for(int j=0;j<n;j++)
            {
                if(comb>>j&1)
                    r.push_back(j+1);
            }
            result.push_back(r);
            int x=comb&-comb;
            int y=comb+x;
            comb = (((comb&~y)/x)>>1)|y;
        }
        return result;
    }
};

结果:100ms

241. Different Ways to Add Parentheses | Difficulty: Medium

Given a string of numbers and operators, return all possible results from computing all the different possible ways to group numbers and operators. The valid operators are +, - and *.

Example 1
Input: “2-1-1”.

((2-1)-1) = 0
(2-(1-1)) = 2
Output: [0, 2]

Example 2
Input: “2*3-4*5”

(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10
Output: [-34, -14, -10, -10, 10]

题意:寻找不同的加括号方式,将可能得到的计算值返回。
思路:
1、首先思考一个问题,先不考虑如何计算具体加括号之后的数值,先想下有多少种加括号的方法?经过试验:有1个运算符只有一个划分方式,有2个运算符有2种划分方式,有3个运算符有5种划分方式。是不是对这个式子很熟悉,没错,加括号的方法数就是一个卡特兰数,当算式中有n个运算符的时候,一共就有f(n)=(2n!)/(n!)(n+1)!种的加括号方法,那么如何做才能做到不重复不遗漏的将每种情况都考虑进来呢?
首先,问题整体来看有些复杂,这个时候可以尝试去找下问题的子问题,从一个简单栗子来看:
2*5-3,这个式子只有2个运算符,我想一个小学毕业生应该都能知道划分方式有((2*5)-3)=7和(2*(5-3))=4这两种,两个运算符是很好看出来的,我们首先考虑第一个号,我们将号左边的结果记为res1,*号右边的结果记为res2,那么我们只需要先后计算出res1和res2,然后最后再两者相乘就是最终结果,而计算res1和res2中结果的时候,也是一样的思想,对res1中的每个算式遍历一次,对每次选中的运算符又分成两个子问题,这样递归的执行下去直到平凡情况也就是res1和res2都是一个数值,这个时候直接对两个数进行四则运算就可以了。
基于以上思路写出如下代码:

class Solution {
public:
    vector<int> diffWaysToCompute(string input) {
        vector<int> res;
        for(int i=0;i<input.length();i++)
        {
            if(input[i]=='+'||input[i]=='-'||input[i]=='*')
            {
                string inputLeft = input.substr(0,i);
                string inputRight = input.substr(i+1,input.length()-i-1);
                vector<int> resLeft = diffWaysToCompute(inputLeft);
                vector<int> resRight = diffWaysToCompute(inputRight);
                for(auto res1:resLeft)
                    for(auto res2:resRight)
                    {
                         switch (input[i]) {
                             case '+':
                                res.push_back(res1+res2);
                                break;
                             case '-':
                                res.push_back(res1-res2);
                                break;
                             default:
                                res.push_back(res1*res2);
                         }
                    }
            }
        }
        if (res.empty())
        {
            res.push_back(stoi(input));
        }
        return res;
    }
};

结果:8ms

2、详见https://discuss.leetcode.com/topic/19906/c-4ms-recursive-dp-solution-with-brief-explanation
DP的思路,开创额外的空间用来保存子式的中间计算的结果,栗如:3*3*3,在以第一个分割的时候,会保存dp[“3”]=3,dp[“3*3”]=9,然后在以第二个分割的时候,左边的3*3和右边的3都在dp中有所保存,直接调用无需再去递归求。

class Solution {
public:
    vector<int> diffWaysToCompute(string input) {
        unordered_map<string, vector<int>> dp;
        return computeWithDP(input, dp);
    }
        vector<int> computeWithDP(string input, unordered_map<string, vector<int>> &dp)
        {
        vector<int> res;
        for(int i=0;i<input.length();i++)
        {
            if(input[i]=='+'||input[i]=='-'||input[i]=='*')
            {
                vector<int> resultLeft,resultRight;
                string inputLeft = input.substr(0,i);
                if(dp.find(inputLeft)!=dp.end())    resultLeft = dp[inputLeft];
                else    resultLeft = computeWithDP(inputLeft,dp);
                string inputRight = input.substr(i+1,input.length()-i-1);
                if(dp.find(inputRight)!=dp.end())    resultRight = dp[inputRight];
                else    resultRight = computeWithDP(inputRight,dp);
                for(auto res1:resultLeft)
                    for(auto res2:resultRight)
                    {
                         switch (input[i]) {
                             case '+':
                                res.push_back(res1+res2);
                                break;
                             case '-':
                                res.push_back(res1-res2);
                                break;
                             default:
                                res.push_back(res1*res2);
                         }
                    }
            }
        }
        if (res.empty())
        {
            res.push_back(stoi(input));
        }
        dp[input] = res;
        return dp[input];
    }
};

结果:4ms

46. Permutations | Difficulty: Medium

Given a collection of distinct numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

题意:写出一个列表中元素所有排列可能性
1、python有直接现成的库可以调用

class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        import itertools
        permute = list(itertools.permutations(nums))
        permute = [list(i) for i in permute]
        return permute

结果:86ms

2、但是做题不能总是import,so考虑用下迭代器

class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        def gen(nums):
            if not nums:
                yield []
            for i, n in enumerate(nums):
                for p in gen(nums[:i] + nums[i+1:]):
                    yield [n] + p
        return list(gen(nums))

结果:92ms

3、可以用DFS来做

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        dfs(nums,0,res);
        return res;
    }
    void dfs(vector<int>& nums,int start,vector<vector<int>>& res)
    {
        if(start>=nums.size())
        {
            res.push_back(nums);
            return ;
        }
        for(int i=start;i<nums.size();i++)
        {
            swap(nums[i],nums[start]);
            dfs(nums,start+1,res);
            swap(nums[i],nums[start]);
        }
    }
};

结果:12ms

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值