LintCode刷题记录

动态规划

125. 背包问题 II

563. 背包问题 V

前言

使用 Google Chrome浏览器,添加插件 九章刷题小助手 可以看到较优答案。

  • 本文参考程序来自九章算法,由 @ 提供。版权所有,转发请注明出处。
  • 九章算法致力于帮助更多中国人找到好的工作,教师团队均来自硅谷和国内的一线大公司在职工程师。
  • 现有的面试培训课程包括:九章算法班,系统设计班,算法强化班,Java入门与基础算法班,Android 项目实战班,
  • Big Data 项目实战班,算法面试高频题班, 动态规划专题班
  • 更多详情请见官方网站:http://www.jiuzhang.com/?source=code

算法记录

1. a+b问题

算法详解

给出两个整数 a 和 b , 求他们的和。

我的想法:

除了 return a+b; 我没什么想法……

九章解法:
class Solution {
    public int aplusb(int a, int b) {
        while (b != 0) {
            int _a = a ^ b;
            int _b = (a & b) << 1;
            a = _a;
            b = _b;
        }
        return a;
    }
};
  1. a^b: a与b按位异或;由于 0^0=0; 0^1=1; 1^0=1; 1^1=0,所以异或的结果就是a和b相加之后,该进位的地方不进位(异或运算有一个别名叫不进位加法)。
  2. a&b: a与b按位与;0&0=0; 0&1=0; 1&0=0; 1&1=1
  3. <<: 位运算符,对一个数的二进制进行移位。 例如,a=(a<<2), 如果a的二进制是 0000 0011,经过运算,左移两位 0000 1100,后面补零。

我用二进制的 7+150111 + 1111 ,逐步推算,结果完全正确。
原理用十进制来解释类似于:7+15 不进位相加得:12,记录进位在第一位:01,左移一位:10,是实际进位的数值,相加的最终结果:12+10=22。

public class Solution {
    public int aplusb(int a, int b) {
        // write your code here
        if (b == 0) 
            return a; 
        else
        return aplusb( a ^ b, (a & b) << 1); 
    }
}

算法分析

  1. 这样的效率更高吗?
  2. 该方法是否只限定于整数?
  3. 举个负数的例子

2. 尾部的零

算法详解

设计一个算法,计算出n阶乘中尾部零的个数

我的想法:

因为偶数比5的倍数分布更稠密,所以能分出来几个5,就有几个零
注意25、125等5的次方可以分出来多个5。
所以应该先整除5,再找离它最近的5的次方的倍数。
例如:377 377/5=75,所以至少有75个0,再找25或125的倍数,377=125*3+2;一个125多出2个5,3个多出来6个5;然后是25,25和125之间有4个25的倍数,一个25多一个5,4个多4个5;所以总共有 75+6+4=85个5,就有这么多0。但答案是93个,惊,哪里还少了呀 。对了,还有25的6倍,7,8,9,11,12,13,14倍,够了93个。所以是不是可以这样做,377/5=75;377/25=15;377/125=3;75+15+3=93;15个25里应该有30个5,但75个5里包含了15个,3个125应该有9个5,但75个5里包含了3个,15个25里包含了3个,最后也只剩3个了。

用这个方法再试一个789,789/625=1;789/125=6;789/25=31;789/5=157;
157+31+6+1=195;
答案对了,所以关键是找到比 n 小的5的最大的次方
这么找行不行,789/5=157,157/5=31,31/5=6,6/5=1,1/5=0,直到得0。

class Solution {
public:
    long long trailingZeros(long long n) {
    	long long sum=0;
    	while(n){
    		n=n/5;
    		sum=sum+n;	
    	}
    	return sum;
    }
};
九章解法:

和我的思路一样的,哈哈哈,还是很有成就感的。

class Solution {
public:
    long long trailingZeros(long long n) {
        long long sum = 0;
        while (n != 0) {
            sum += n / 5;
            n /= 5;
        }
        return sum;
    }
};

算法分析

出错,分析,改正,出错,分析,改正……循环往复,得到正解

3. 统计数字

算法详解

计算数字 k 在 0 到 n 中的出现的次数,k 可能是 0~9 的一个值。
例如:
输入:k = 1, n = 12
输出:5
解释:在 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 中,我们发现 1 出现了 5 次 (1, 10, 11, 12)(注意11中有两个1)。

我的想法:

举个例子:234
想法一:
一位数:0-9;个位循环1次
二位数:10-99;个位循环9次; 十位1-9各10个
三位数:100-199;个位循环10次;十位各10个;百位1有100个
200-229;个位循环3次;十位0-2各10个;百位2有30个
230-234:个位0-4各一个,十位3有5个,百位2有5个
合计:

0123456789
4415489

想法一虽然正确,但规律性不高,不太容易编程,想到想法二:
每10个数分一组,个位0到9每10个数完成一次循环,
到了二位数以后,个位的规律不变,十位每10个数加1,每100个数完成一次循环,
到了三位数,个位、十位的规律不变,百位每100个数加1,每1000个数完成一次循环,
最后再注意一下0,0不能做数字开头,算多的要减去

还是以234为例
234/10=23+4;每10个数分一组,个位完成23次循环,剩余 0-4 五个数
234/100=2+34;每100个数分一组,十位完成2次循环,每个数加20;剩余35个数,0-2各10次,3 五次。
234/1000=0+234;百位没有发生循环;剩余235个数,0-1各100次,2 三十五次

0不能做开头,二位数是算0做开头 -10;三位数是算0做开头 -100;
合计:

0123456789
4415489

分析,
1、程序可以由循环实现,n是几位数,循环几次
2、第几次循环就用n/(10的几次),需要用到商和余数
3、第k次循环,商 乘 (10的k-1次) 表示因完成循环增加的次数
                         余数 除 (10的k-1 ) 的商 表明未完成的循环进行到了几,例如商是3,0-2 完成,各加 10的k次,余数 除 (10的k-1 ) 的余数 表明最后的数的次数,例如商是3,3的个数加 余数+1
4、每次循环判断 数 是否是 0,如果是,每次减去 (10的k-1)次。循环第一次不减

class Solution {
public:
    /**
     * @param k: An integer
     * @param n: An integer
     * @return: An integer denote the count of digit k in 1..n
     */
    int digitCounts(int k, int n) {
        // write your code here
        //先判断n是几位数
        int num=0;  //默认是0位数
        int count=0; //记录k出现的次数
        int _n=n; 
        while(_n/10){
	        _n/=10;
	        num+=1;
        }
        num+=1;
        //得到n的位数 num
        for(int i=1;i<=num;i++){
	        int _s=n/pow(10,i);  //商
	        int _y=n-_s*pow(10,i);  //余数
	        count+=_s*pow(10,i-1);
	        int _ys=_y/pow(10,i-1);
	        int _yy=_y-_ys*pow(10,i-1);
	        if(k<_ys){
		        count+=pow(10,i-1);
	        }else if(k==_ys){
		        count+=_yy+1;
	        }
	        if(k==0&&i!=1){
		        count-=pow(10,i-1);
	        }	
        }
        return count;
    }
};
九章解法:

这个答案好暴力,直接把每个数的每一位都提取出来判断,思路很简单,但效率不高吧。

class Solution {
 public:
    int digitCounts(int k, int n) {
        int count = 0;
        if (k == 0) {
            count = 1;
        }
        for (int i = 1; i <= n; i++) {
            int number = i;
            while (number > 0) {
                if (number % 10 == k) {
                    count += 1;
                } 
                number /= 10;
            }
        }  
        return count;
    }
};

算法分析

感觉 lintcode 的效率分析会受电脑性能等的影响,并不完全准确。

4. 丑数 II

算法详解

设计一个算法,找出只含素因子2,3,5 的第 n 小的数。(找到第n个丑数)
符合条件的数如:1, 2, 3, 4, 5, 6, 8, 9, 10, 12…
样例 1:
输入:9
输出:10
样例 2:
输入:1
输出:1
挑战
要求时间复杂度为 O(nlogn) 或者 O(n)。
注意事项
我们可以认为 1 也是一个丑数。

我的想法:

想法一:
从2开始找,一个数一个数判断,找到一个丑数,计数变量加一,直到计数变量等于n

class Solution {
public:
    int nthUglyNumber(int n) {
        int count=1;
        int num=2;   //从2开始,依次判断
        int targ=1;    //找到的丑数赋值给 targ;
        while(count!=n){
            //判断num是不是丑数,如果是 count+1,且赋值给 targ
            int _num=num;
            while(_num%2==0){
                _num/=2;
            }
            if(_num!=1){
                while(_num%3==0){
                    _num/=3;
                }
                if(_num!=1){
                    while(_num%5==0){
                        _num/=5;
                    }
                }
            }
            if(_num==1){
                targ=num;
                count+=1;
            }
            num+=1;
        }
        return targ;
    }
};

可以实现,但时间复杂度太高,当n变大时,丑数越来越稀疏,需要好久才能算出来。

九章解法:
class Solution {
public:
    int nthUglyNumber(int n) {
        int *uglys = new int[n];   //创建数组,存储丑数
        uglys[0] = 1; //第一个丑数是1
        int next = 1; //循环变量,递增,循环n次,每次循环找到一个丑数
        int *p2 = uglys; //指针,指向丑数数组首地址 uglys[0]
        int *p3 = uglys; //指针,指向丑数数组首地址 uglys[0]
        int *p5 = uglys; //指针,指向丑数数组首地址 uglys[0] 
        //开始循环
        while (next < n){
            int m = min(min(*p2 * 2, *p3 * 3), *p5 * 5);
            uglys[next] = m;
            while (*p2 * 2 <= uglys[next])
                *p2++;
            while (*p3 * 3 <= uglys[next])
                *p3++;
            while (*p5 * 5 <= uglys[next])
                *p5++;
            next++;
        }
        int uglyNum = uglys[n - 1];
        delete[] uglys;
        return uglyNum;
    }
};

在这里插入图片描述
④下一个丑数一定是已有的丑数里的某一个乘2、3或5
循环过程是:
1乘2得2,1乘3得3,1乘5得5,2最小,下一个丑数是2,
1乘2已经使用,p2指向2,1乘3、乘5还没有用过,p3、p5依然指向1

算法分析

九章解法,由丑数得丑数,更加快捷

5. 第k大元素

算法详解

在数组中找到第 k 大的元素。
样例 1:
输入:n = 1, nums = [1,3,4,2]
输出:4
样例 2:
输入:n = 3, nums = [9,3,2,4,8]
输出:4
挑战
要求时间复杂度为O(n),空间复杂度为O(1)。
注意事项
你可以交换数组中的元素的位置

我的想法:

这个之前有过接触,我当时是全部排序之后,输出第k大元素,但这样效率太低了,而且没有必要,因为只要第k大,所以全部排序也是无用功,只需要排列前k个就行了。
所以思路就是前k个元素由大到小排列好,其余的依次往里插,找到自己的位置,所以最后就只得到前k大元素。 使用折半排序


九章解法:
class Solution {
public:
    int kthLargestElement(int k, vector<int> nums) {
        if (nums.size() == 0 || k < 1 || k > nums.size()){
            return -1;
        }
        return partition(nums, 0, nums.size() - 1, nums.size() - k);
    }
        
private:
    int partition(vector<int> &nums, int start, int end, int k) {
        if (start >= end) {
            return nums[k];
        }
        
        int left = start, right = end;
        int pivot = nums[(start + end) / 2];
        
        while (left <= right) {
            while (left <= right && nums[left] < pivot) {
                left++;
            }
            while (left <= right && nums[right] > pivot) {
                right--;
            }
            if (left <= right) {
                swap(nums, left, right);
                left++;
                right--;
            }
        }
        
        if (k <= right) {
            return partition(nums, start, right, k);
        }
        if (k >= left) {
            return partition(nums, left, end, k);
        }
        return nums[k];
    }    
    
    void swap(vector<int> &nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
};

算法分析

32. 最小子串覆盖

算法详解

给定两个字符串 source 和 target. 求 source 中最短的包含 target 中每一个字符的子串.
样例 1:
输入: source = “abc”, target = “ac”
输出: “abc”
样例 2:
输入: source = “adobecodebanc”, target = “abc”
输出: “banc”
解释: “banc” 是 source 的包含 target 的每一个字符的最短的子串.
样例 3:
输入: source = “abc”, target = “aa”
输出: “”
解释: 没有子串包含两个 ‘a’.
挑战
O(n) 时间复杂度
注意事项
如果没有答案, 返回 “”.
保证答案是唯一的.
target 可能包含重复的字符, 而你的答案需要包含至少相同数量的该字符.

我的想法:
九章解法:
class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> mp;
        for (char now : t) {
            mp[now] ++;
        }
        int count = mp.size();
        int j = 0;
        int ans = INT_MAX;
        string res; 
        for (int i = 0; i < s.size(); i++) {
            while(count != 0 && j < s.size()) {
                mp[s[j]]--;
                if (mp[s[j]] == 0) {
                    count--;
                }
                j++;
                if (count == 0) {
                    break;
                }
            }
            if (count == 0 && j - i< ans) {
                ans = j - i;
                res = s.substr(i, j - i);
                
            }
            if(mp[s[i]] == 0) {
                count++;
            }
            mp[s[i]]++;
        }
        return res;
    }
};

算法分析

384. 最长无重复字符的子串

算法详解

给定一个字符串,请找出其中无重复字符的最长子字符串。
样例 1:
输入: “abcabcbb”
输出: 3
解释: 最长子串是 “abc”.
样例 2:
输入: “bbbbb”
输出: 1
解释: 最长子串是 “b”.
挑战
O(n) 时间复杂度

我的想法:
九章解法:

外层循环是右指针,左指针根据情况移动到合适的位置。

class Solution {
public:
    int lengthOfLongestSubstring(string& s) {
        // 题意为求不包含重复字符的最长子串
        // left用以记录合法的最远左边界位置,last记录字符上一次出现的位置
        int ans = 0, left = 0, len = s.length();
        int last[255];
        memset(last, -1, sizeof last);
        
        for (int i = 0; i < len; i++) {
            // 上次出现位置在当前记录边界之后,即该子串中出现了重复字符,需调整left使得子串合法
            if (last[s[i]] >= left) left = last[s[i]] + 1;
            last[s[i]] = i;
            ans = max(ans, i - left + 1);
        }
        return ans;
    }
};

算法分析

406. 和大于S的最小子数组

算法详解

给定一个由 n 个正整数组成的数组和一个正整数 s ,请找出该数组中满足其和 ≥ s 的最小长度子数组。如果无解,则返回 -1。
样例 1:
输入: [2,3,1,2,4,3], s = 7
输出: 2
解释: 子数组 [4,3] 是该条件下的最小长度子数组。
样例 2:
输入: [1, 2, 3, 4, 5], s = 100
输出: -1
挑战
如果你已经完成了O(nlogn)时间复杂度的编程,请再试试 O(n)时间复杂度。

我的想法:

由大到小排序后从前往后选,肯定可以实现,但这样会有浪费
可以先找出最大的,不行的话,在剩下的数据中在找到最大的。每次只找最大的

思路错了,子数组只能是连续的

九章解法:

九章视频课中讲解了解法的优化
题目类型是 寻找符合条件的子数组

初始化:
获取数组长度:int n=nums.size(); 
初始化左右指针(开始时都指向数组第一个元素):int right=0,left=0;
初始化子数组的和:int total=0;
初始化符合条件的子数组的长度:int ans=n+1;

开始寻找:
右指针依次右移,直到移动到数组末位
while(right<n){
	right++;
}
右移过程中子数组每次新增一个元素,累加total
while(right<n){
	total+=nums[right++];
}
累加过程中判断total值是否大于等于s,如果大于等于更新ans
while(right<n){
	total+=nums[right++];
	if(total>=s){
			ans=min(ans,right-left+1);
	}
}
此时,暂停右指针的移动,左指针右移,直到与right重合,或者total值小于了s
while(right<n){
	total+=nums[right++];
	if(total>=s){
		ans=min(ans,right-left);
		while(left<right&&total>=s){
			total-=nums[left++];
			if(total>=s){
				ans=min(ans,right-left);
			}
		}
	}
}

答案写法,left是外层循环

    int minimumSize(vector<int> &nums, int s) {
        int n = nums.size();
        int j=0;
        int left = 0, right = 0, total = 0, ans = n + 1;
        for(int i=0;i<n;i++){
            while(j<n&&total<s){
                total+=nums[j++];
            }
            if(total>=s){
                ans=min(ans,j-i);
            }
            total-=nums[i];
        }
        if (ans == n + 1) 
            return -1;
        else 
            return ans;
    }

算法分析

2000. 模板

算法详解

题目:

我的想法:
九章解法:

算法分析

九章算法强化班学习

  1. 题目永远做不完,要学会使用 一个模板解决一类题目

子串问题

问题特点

在一个数组或字符串中找到连续的符合条件的最短或最长的子串

题目举例

lintcode406题
lintcode384题
lintcode32题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值