秋招/春招常见笔试题目——数组系列(C/C++)

        大家好!下面是我(一个小小的搬运工)在秋招的时候在Leetcode上整理的一些与数组相关的题目(中等难度)笔试和面试考相似思路题目的概率比较大,大家如果准备春秋季招聘可以先根据这些题目复习(具体思路可以看Leetcode中的讲解——困难的题有链接):

//https://blog.csdn.net/qq_41855420/column/info/38495    c++STL模板剖析


https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/solution/yi-ge-tong-yong-fang-fa-tuan-mie-6-dao-gu-piao-wen/
股票问题!!!


/*

最大堆——子节点的值比父节点的值都要小,即根节点为最大值。(大根堆)

最小堆——子节点的值比父节点的值都要大,即根节点为最小值。(小根堆)




最大堆和最小堆问题:
1、multimap> freq;//   (less)最小堆  (greater)最大堆    与make_heap()正好相反

2、multiset > res;             //   (less)最小堆  (greater)最大堆    与make_heap()正好相反



3、priority_queue>     //    最小堆

4、    
	make_heap(nums.begin(), nums.end(), less());       //创建最大堆
	make_heap(nums.begin(), nums.end(), greater());    //创建最小堆

	//https://www.jianshu.com/p/13a56502e217


*/

//2019_05_09
//1、给定一个未排序的数组,在O(n)的时间复杂度内找到其最大连续子串的长度
Given[100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is[1, 2, 3, 4]. Return its length:4.
//用散列表,首先将数字都映射到散列表上,然后,对于每个数字,找到后就删除,然后向两边
//同时搜,只要搜到了就删除,最后求出长度。哈希表搜是O(1),因为每个数字只会添加一次,
//删除一次,所以复杂度是O(n)
class Solution {
public:
    int longestConsecutive(vector &num) {
            unordered_set st(num.begin(),num.end());
            int ans=0;
            for(auto v:num){       //c++11新特性,v表示num迭代器中的每一个
                if(st.find(v)==st.end()) continue;
                int l=v,r=v;
                st.erase(v);
                while(st.find(r+1)!=st.end()) st.erase(++r);
                while(st.find(l-1)!=st.end()) st.erase(--l);
                ans=max(r-l+1,ans);
            }
            return ans;
    }
};
/**
动态规划问题:  (1)将问题分解成子问题(2)找出状态转移方程(3)求出边界条件!!!!!
**/
//2、卖股票问题(给定一个数组,表示每天股票的价格,可以无数次交易(先买再卖),求最大利润)
//有买(buy)和卖(sell)两种状态
//若当天买股票,则损失-prices[i],若卖股票,则增加prices[i]
//那么当天卖出去的总收益为buy+prices[i]
// 需要考虑(1):当天买和之前买哪个损失少,即buy是负数 (2):当天卖和之前卖哪个收益高,即sell是正数
// 状态转移方程: sell=max(sell,buy+prices[i])   buy=max(buy,sell-prices[i])



class Solution {
public:
    int maxProfit(vector &prices) {
        if(prices.size()==0)
            return 0;
        int buy=-prices[0],sell=0;
        for(int i=0;i、<和=。如果A[k/2-1] < B[k/2-1],这表示A[0]到A[k/2-1]的元素都在A和B合并之后的前k小的元素中。换句话说,A[k/2-1]不可能大于两数组合并之后的第k小值,所以我们可以将其抛弃。

当A[k/2-1]=B[k/2-1]时,我们已经找到了第k小的数,也即这个相等的元素,我们将其记为m。由于在A和B中分别有k/2-1个元素小于m,所以m即是第k小的数。(这里可能有人会有疑问,如果k为奇数,则m不是中位数。这里是进行了理想化考虑,在实际代码中略有不同,是先求k/2,然后利用k-k/2获得另一个数。)

通过上面的分析,我们即可以采用递归的方式实现寻找第k小的数。此外我们还需要考虑几个边界条件:

如果A或者B为空,则直接返回B[k-1]或者A[k-1];
如果k为1,我们只需要返回A[0]和B[0]中的较小值;
如果A[k/2-1]=B[k/2-1],返回其中一个
*/
class Solution {
public:
    double findMedianSortedArrays(int A[], int m, int B[], int n) {

        int total = m + n;
        if(total & 0x1)
            return findKth(A, m, B, n, total / 2 + 1);
        else
            return (findKth(A, m, B, n, total/2)
                   + findKth(A, m, B, n, total / 2 + 1)) / 2;
    }
double findKth(int A[], int m, int B[], int n, int k){
        if(m > n)
            return findKth(B, n, A, m, k);  //始终保持元素较少的数组位于前面的位置
        if(m == 0)
            return B[k-1];                  //如果位于前面的数组为空,则直接返回后面数组的第k-1个元素
        if(k == 1)
            return min(A[0], B[0]);         //如果k等于1,则返回两个数组头元素的最小值
 
        int pa = min(k / 2, m), pb = k - pa;
        if(A[pa-1] < B[pb-1])
            return findKth(A + pa, m - pa, B, n, k - pa);
        else if(A[pa - 1] > B[pb - 1])
            return findKth(A, m, B + pb, n - pb, k - pb);
        else
            return A[pa-1];
    }
    
};

//2、翻转一个整数(要考虑正负号、小数、溢出等情况)

//本体关键点是如何判断溢出。
//推荐解答用的是用long类型存储结果,如果结果大于0x7fffffff或者小于0x80000000就溢出

//当res*10+tail的值超过最大位数时,系统会将超过最大位数的高位二进制数扔掉,从而导致了newRes!=res*10+tail.
//此时计算的(newRes-tail)/10自然也不等于res了。

class Solution {
public:
    int reverse(int x)
    {
        int res=0;
        while(x!=0){
            //最后一位
            int tail=x%10;
            int newRes=res*10+tail;
            //如果newRes-tail)/10!=res说明产生了溢出
            if((newRes-tail)/10!=res)
                return 0;
            res=newRes;
            x=x/10;
        }
        return res;
    }
};

//3、给定一个未排序的数组,找出和为0的三个数
void dfs(set > &res, vector &tmp, vector &num,int &count,int index)
{
	if (count == 3)
	{
		if (tmp[0] + tmp[1] + tmp[2] == 0)
			res.insert(tmp);
		return;
	}
	for (int i = index; i < num.size(); i++)
	{
		tmp.push_back(num[i]);
		count++;
		dfs(res,tmp,num,count,i+1);
		count--;
		tmp.pop_back();	
	}
}

int main()
{
	set > res;
	vector tmp;;

	int a[6] = {-1,0,1,2,-1,-4};
	vector num(a, a + 6);
	sort(num.begin(), num.end());
	int count = 0;
	dfs(res, tmp, num, count, 0);
	return 0;
}

//2019_05_23
//1、在未排序的数组中找到第 k 个最大的元素(采用最小堆实现)
class Solution {
public:
	int findKthLargest(vector& nums, int k) {

		multiset > res;    //   (less)最小堆  (greater)最大堆          与make_heap()正好相反
		if (k < 1 || nums.size() < k)
			return 0;
		multiset >::iterator it;
		vector::iterator iter = nums.begin();

		for (; iter != nums.end(); iter++)
		{
			if (res.size()*(res.begin()))
				{
					res.erase(it);
					res.insert(*iter);

				}


			}

		}
		it = res.begin();
		return *it;
	}
};
//2、采用make_heap()创建最大堆和最小堆     
//https://www.jianshu.com/p/13a56502e217
//make_heap(nums.begin(), nums.end(), less());       创建最大堆
//make_heap(nums.begin(), nums.end(), greater());    创建最小堆

//2019_05_28
/*
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]。

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8

输出: [3,4]
*/
/*
总体算法工作过程与线性扫描方法类似,除了找最左和最右下标的方法。这里我们仅仅做几个微小的调整,用这种修改过的二分查找方法去搜索这个排过序的数组。首先,为了找到最左边(或者最右边)包含 target 的下标(而不是找到的话就返回 true ),所以算法在我们找到一个 target 后不能马上停止。我们需要继续搜索,直到 lo == hi 且它们在某个 target 值处下标相同。

另一个改变是 left 参数的引入,它是一个 boolean 类型的变量,指示我们在遇到 target == nums[mid] 时应该做什么。如果 left 为 true ,那么我们递归查询左区间,否则递归右区间。考虑如果我们在下标为 i 处遇到了 target ,最左边的 target 一定不会出现在下标大于 i 的位置,所以我们永远不需要考虑右子区间。当求最右下标时,道理同样适用。
*/

class Solution {
public:
    vector searchRange(vector& nums, int target) {        
        vector res={-1,-1}; 
        int left = extremeInsertionIndex(nums, target, true);
        // assert that `left` is within the array bounds and that `target`
        // is actually in `nums`.
        if (left == nums.size() || nums[left] != target) {
            return res;
        }
        res[0] = left;
        res[1] = extremeInsertionIndex(nums, target, false)-1;

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

        return left;
    }
    
};

//2019_05_29
/*
给出一个区间的集合,请合并所有重叠的区间。

示例 1:

输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
*/
/*
首先题目并没有说明数组是排序的,所以我们第一步进行排序,根据数组第一位升序排序,一旦第一位相等则按第二位降序排序。
第二,由于在原数组进行更改的话需要花费太多时间在删除元素和更改元素上,所以我们声明一个二维数组存放结果,一个一维数组暂时存放要插入的数据。
由于是排序过的,所以我们在判断的时候先判断区间下边界是否大于等于下个区间的上边界,若是则可以判断出此时两区间存在重复,反之两区间不重复;继续判断区间的下边界是否小于下个区间的下边界,若是则说明两区间存在交叉,反之说明是区间包含
*/

class Solution {
public:
    static bool cmp(const vector&a,const vector&b )
    {
        if(a[0]==b[0])
            return a[1]>b[1];
        return a[0]> merge(vector>& intervals) {
        if(intervals.empty()) return intervals;
        vector> res;
        int count=0;
        sort(intervals.begin(),intervals.end(),cmp);
        vector temp;
        temp.push_back(intervals[0][0]);
        temp.push_back(intervals[0][1]);
        res.push_back(temp);
        for(int i=1;i=intervals[i][0])
            {
                if(res[count][1]<=intervals[i][1])
                {
                    res[count][1]=intervals[i][1];
                }
            }
            else
            {
                count++;
                temp[0]=intervals[i][0];
                temp[1]=intervals[i][1];
                res.push_back(temp);
            }
        }
        return res;
    }
};

//2019_05_30
/*
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

示例 1:

输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
*/
class Solution {
public:
    int findUnsortedSubarray(vector& nums) {
        int m=nums[0],n=nums.back();
        int left=-1,right=0;
        int len=nums.size();
        for(int i=1;i& nums) {
        int m = nums[0], n = nums.back(), l = -1, r = -2;
        int len = nums.size();
        for (int i = 1; i < len; ++i)
        {
            m = max(m, nums[i]);
            n = min(n, nums[len - 1 - i]);
            if (m != nums[i]) r = i;
            if (n != nums[len - 1 - i]) l = len - 1 - i;
        }
        return r - l + 1;
    }
};
*/

//2019_05_31
/*
1、根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高的天数。如果之后都不会升高,请输入 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]
*/
/*
维护递减栈,后入栈的元素总比栈顶元素小。

比对当前元素与栈顶元素的大小
    
    若当前元素 < 栈顶元素:入栈
    若当前元素 > 栈顶元素:弹出栈顶元素,记录两者下标差值即为所求天数
        
        这里用栈记录的是 T 的下标
*/


class Solution {
public:
    vector dailyTemperatures(vector& T) {
        vector  ans (T.size(), 0);
        stack  res;
        for(int i = T.size()-1; i >= 0; --i)
        {
            while(!res.empty() && T[i] >= T[res.top()]) 
                res.pop();
            if(res.empty())
                ans[i] = 0;
            else
                ans[i] = res.top() - i;
            
            res.push(i);
        }
        
        return ans;
    }
};

/*   算法超时
class Solution {
public:
    vector dailyTemperatures(vector& T) {
        vector res;
        if(T.size()==0)
            return res;
        
        int left=0,right=0,mid=0;
        for(left=0;leftT[left]){
                    mid=right-left;
                    res.push_back(mid);
                    break;
                }
            else if(right==T.size()-1)
                    res.push_back(0);           
            }
           if(left==T.size()-1)
                    res.push_back(0);             
        }
        return res;
    }
};
*/

/*
2、给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:

输入: nums = [1], k = 1
输出: [1]
*/
/*
class Solution {
public:
    vector topKFrequent(vector& nums, int k) {
        map mp;
        for(auto num:nums)
            mp[num]++;
        vector> vec(mp.begin(),mp.end());
        sort(vec.begin(),vec.end(),cmp);
        vector  res;
        for(int i=0;i &x,const pair &y ){
        return x.second>y.second;
    }
};
*/
class Solution {
public:
    vector topKFrequent(vector& nums, int k) {
        unordered_map count;//计算频率
        for (auto &num: nums)
            count[num]++;
        multimap> freq;               //用于排序,最大堆
        for (auto &f : count)
            freq.insert(make_pair(f.second, f.first));
        vector res;//存放结果
        for (auto it = freq.begin(); it != freq.end() && k; ++it,--k)
            res.push_back(it->second);
        return res;
    }
};
//2019_06_03
/*
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
*/
class Solution {
public:
    int maxSubArray(vector& nums) {
        if(nums.size() == 0) return NULL;
        int res = INT_MIN;
        int sum = -1;
        for(int i = 0; i < nums.size(); ++i)
        {
            sum = max(nums[i], sum + nums[i]);
            res = max(sum, res);
        }
        return res;
    }
};

/*
通过前三项可以总结出规律:dp[i] = max(dp[i-1] + nums[i], nums[i]) 如果选中当前项,则更改head指向当前项
class Solution {
    public int maxSubArray(int[] nums) {

        if(nums.length == 0) return 0;
        if(nums.length == 1) return nums[0];

        int []dp = new int[nums.length];
        int head = 0;
        dp[0] = nums[0];

        for(int i = 1; i < nums.length; i++){
            dp[i] = Math.max(dp[i-1] + nums[i], nums[i]); //核心代码
            if(dp[i-1] + nums[i] < nums[i])
                head = i;
        }

        int max = dp[0]; //找最大和
        for(int i = 0; i < dp.length; i++){
            if(max < dp[i])
                max = dp[i];
        }
        
        return max;
        
    }
}


*/

//2019_06_03
/*
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 
示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]
*/
class Solution {
public:
    vector> combinationSum(vector& candidates, int target) {
        vector> res;        
        if(candidates.empty())
            return res;
        sort(candidates.begin(), candidates.end());
        vector npos;
        sum_aux(candidates, res, target, 0, npos);
        return res;
    }
    void sum_aux(vector& candidates, vector>& res, int target, int start, vector& pos)
    {
        if(target == 0) {
            res.push_back(pos);
            return;
        }            
        for(int i = start; i < candidates.size(); ++i)
        {
            if(candidates[i] > target)
                break;
            pos.push_back(candidates[i]);
            sum_aux(candidates, res, target - candidates[i], i, pos);
            pos.pop_back();
        }
    }
};


//2019_06_21                          经典习题!!!!!!!!!!!!!!!!!!!!!!!

//https://leetcode-cn.com/problems/sliding-window-maximum/solution/dan-diao-dui-lie-by-labuladong/
/*
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 k 内的数字。滑动窗口每次只向右移动一位。

返回滑动窗口最大值。

单调队列设计完毕

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[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

*/
class MonotonicQueue {
private:
    deque data;
public:
    void push(int n) {
        while (!data.empty() && data.back() < n) 
            data.pop_back();
        data.push_back(n);
    }
    
    int max() { return data.front(); }
    
    void pop(int n) {
        if (!data.empty() && data.front() == n)
            data.pop_front();
    }
};

vector maxSlidingWindow(vector& nums, int k) {
    MonotonicQueue window;
    vector res;
    for (int i = 0; i < nums.size(); i++) {
        if (i < k - 1) { //先填满窗口的前 k - 1
            window.push(nums[i]);
        } else { // 窗口向前滑动
            window.push(nums[i]);
            res.push_back(window.max());
            window.pop(nums[i - k + 1]);
        }
    }
    return res;
}


//2019_06_22

//给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

/*
由于乘法的特殊性,所以string上每一位的乘积最大值(不论正负),
都只与前一个值能达到的最大值(当前数字为正)和最小值(当前数位为负)有关系。
所以,只存储上一位的最大和最小可能乘积,然后计算并储存,计算后比较该数位最大值与之前所有数位的最大可能出现的乘积,记录。
一次历遍,时间O(n), 空间O(1)。

输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

*/
class Solution {
public:
    int maxProduct(vector& nums) {
        if(nums.empty()) return 0;
        int i, ret, pos, neg, temp;
        pos = nums[0];
        neg = nums[0];
        ret = nums[0];
        for(i = 1; i < nums.size(); i++){
            temp = pos;
            pos = max(nums[i], max(pos * nums[i], neg * nums[i]));
            neg = min(nums[i], min(temp * nums[i], neg * nums[i]));
            ret = max(pos, ret);
        }
        return ret;
    }
};
//2019_06_25    求质数的个数
/*
西元前250年,希腊数学家厄拉多塞(Eeatosthese)想到了一个非常美妙的质数筛法,减少了逐一检查每个数的的步骤,可以比较简单的从一大堆数字之中,筛选出质数来,这方法被称作厄拉多塞筛法(Sieve of Eeatosthese)。

具体操作:先将 2~n 的各个数放入表中,然后在2的上面画一个圆圈,然后划去2的其他倍数;第一个既未画圈又没有被划去的数是3,将它画圈,再划去3的其他倍数;现在既未画圈又没有被划去的第一个数 是5,将它画圈,并划去5的其他倍数……依次类推,一直到所有小于或等于 n 的各数都画了圈或划去为止。这时,表中画了圈的以及未划去的那些数正好就是小于 n 的素数。

作者:gpe3DBjDS1
链接:https://leetcode-cn.com/problems/two-sum/solution/ji-shu-zhi-shu-by-gpe3dbjds1/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/
class Solution {
public:
    int countPrimes(int n) {
        vector num(n,true);
        int res=0;
        for(int i=2;i<=sqrt(n);i++)
        {
            if(num[i])
            {
                int k=2;
                while(k*i int:
        ones, twos, threes = 0, 0, 0
        for num in nums:
            twos |= ones & num               //ones & num 提取两个数都为1的位,与twos作或操作保留出现2次的位
            ones ^= num                      // 当 ones 和 num 同时为 1 or 0 时,ones = 0,因为同时为1已经加到twos里了,这里不做count
            threes = ones & twos             // 当ones和twos对应位都为1时,说明此位出现了3次
            ones &= ~threes                  // three为1的位,将one和two对应位归零
            twos &= ~threes
        return ones

//2019_07_23
/*
给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4 
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

采用动态规划

*/
/*
https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-she-ji-fang-fa-zhi-pai-you-xi-jia/


作者:LeetCode
链接:https://leetcode-cn.com/problems/two-sum/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0) {
            return 0;
        }
        int[] dp = new int[nums.length];
        dp[0] = 1;
        int maxans = 1;
        for (int i = 1; i < dp.length; i++) {
            int maxval = 0;
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    maxval = Math.max(maxval, dp[j]);
                }
            }
            dp[i] = maxval + 1;
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}

//2019_07_23
/*
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:

你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:

输入: [3,1,5,8]
输出: 167 
解释: nums = [3,1,5,8] --> [3,5,8] -->   [3,8]   -->  [8]  --> []
     coins =  3*1*5      +  3*5*8    +  1*3*8      + 1*8*1   = 167

*/

/**
记忆化搜索(递归 + 动态规划)。

nums数组首和尾各扩增一个数值1,以便于计算。
设二维数组dp[start][end],表示扩增后nums数组下标start到end的dp值(即[start,end]区间里能获得硬币的最大数量)。假设下标middle指示的位置为
这个区间里最后一个戳破气球的位置,则dp[start][middle-1]表示该位置左半部分已经戳破气球得到的奖赏,dp[middle+1][end]表示该位置右半部分已经
戳破气球得到的奖赏。此时middle位置上的气球与(start-1),(end+1)位置上的气球相邻,最后戳破middle位置上的气球,因而得到的总奖赏为:
nums[start-1]*nums[middle]*nums[end+1] + dp[start][middle-1] + dp[middle+1][end];
对于[start,end]区间每一个位置都尝试作为最后一个戳破的气球位置,寻找总奖赏最大的作为该区间的dp值,并记录,以便下次用到该区间时,dp值不需重新计算。

特殊情况:当middle指向start或end位置时,继续递归会出现start > end 的情况,此时只需返回0。
**/

class Solution {
private:
    vector> dp;
    vector mnums;
public:
    int maxCoins(vector& nums) {
        if(nums.empty())
            return 0;
        int n;
        nums.insert(nums.begin(), 1);
        nums.insert(nums.end(), 1);
        n = nums.size();
        dp = vector>(n, vector(n, 0));   //初始化所有区间dp值为0
        mnums = nums;                                     //复制nums数组为类成员变量
        return dfs(1, n-2);
    }
    int dfs(int start, int end){
        int middle, left, right;
        if(start > end)                                   //特殊情况
            return 0;
        if(dp[start][end] != 0)                           //备忘录中记录有该数据,返回该数据,不必从新计算
            return dp[start][end];
        for(middle = start; middle <= end; ++middle){     //对区间的每一个位置尝试性作为最后一个戳破的位置,找到最优的位置
            left = dfs(start, middle-1);                  //区间[start,middle-1]的dp值
            right = dfs(middle+1, end);                   //区间[middle+1,end]的dp值
            dp[start][end] = max(dp[start][end], left + right + mnums[start-1] * mnums[middle] * mnums[end+1]);   //加入备忘录
        }
        return dp[start][end];
    }
};

//2019_07_27
/*
1、给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2
输出: [0,1,1]
示例 2:

输入: 5
输出: [0,1,1,2,1,2]

*/

/*
对于所有的数字,只有两类:

奇数:二进制表示中,奇数一定比前面那个偶数多一个 1,因为多的就是最低位的 1。
          举例: 
         0 = 0       1 = 1
         2 = 10      3 = 11
偶数:二进制表示中,偶数中 1 的个数一定和除以 2 之后的那个数一样多。因为最低位是 0,除以 2 就是右移一位,也就是把那个 0 抹掉而已,所以 1 的个数是不变的。
           举例:
          2 = 10       4 = 100       8 = 1000
          3 = 11       6 = 110       12 = 1100
另外,0 的 1 个数为 0,于是就可以根据奇偶性开始遍历计算了。

*/

class Solution {
public:
    vector countBits(int num) {
        vector res(num+1,0);
        res[0]=0;
        
        for(int i=1;i<=num;i++){
            if(i&0x01)
                res[i]=res[i-1]+1;
            else
                res[i]=res[i/2];
            
            
            
            
        }
        return res;

    }
};

/*
2、和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

*/
    /*
	
	第一种方法:
     * 思路:
     *  sum[i]表示num[0:i]的子数组和,那么num[i:j]的和等于sum[j] - sum[i]
     *  两层循环遍历sum数组检查 sum[j] - sum[i] == k
     *
     * @param nums 数组
     * @param k 目标和
     * @return 子数组个数
    */


class Solution {
public:
    int subarraySum(vector& nums, int k) {
        int cur = 0, res = 0;
        unordered_map ans;
      	// 注意这里前缀和多了一个0,防止漏掉数组的前缀和刚好等于k的情况
        ans[0] = 1;
        for (int num : nums) {
            cur += num;
            res += ans.find(cur - k) == ans.end() ? 0 : ans[cur - k];
            ++ans[cur];
        }
        return res;
    }
};
/*
第二种方法:递归
*/
class Solution {
public:
    int out = 0;
    void result(int idx, vector& nums, int k, int sum)
    {
        if (sum == k)
        {
            out++;
        }   
        if (idx >= nums.size() - 1)
        {
            return;
        }
        result(idx + 1, nums, k, sum+nums[idx+1]);
    }
    int subarraySum(vector& nums, int k) {
        for (int i = 0; i < nums.size(); i++){
            result(i, nums, k, nums[i]);
        }
        return out;
    }
};

//2019_07_28
/*
1、给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[5,6]

*/

class Solution {
public:
    vector findDisappearedNumbers(vector& nums) {
    vector res;

	for (int i = 0; i < nums.size(); i++){
		while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]){

			swap1(nums,i,nums[i]-1);
		}
	}

	for (int i = 0; i < nums.size(); i++){
		if (nums[i] != i + 1)
			res.push_back(i+1);

	}

	return res;
    }
    
    void swap1(vector& nums, int i, int j){

	nums[i] = nums[i] ^ nums[j];
	nums[j] = nums[i] ^ nums[j];
	nums[i] = nums[i] ^ nums[j];
	}
    
};

/*
2、给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].
 

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集

*/
/*
排序后从大往小DFS,从小往大会被1,1,1,1,......这样的数据卡TLE 一旦出现超过sum剪枝


https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/

*/
class Solution {
public:
    bool DFS(int i,int cur,int sum,vector&nums){
        for(int j=i-1;j>=0;j--){
            if(cur+nums[j]==sum) return true;
            if(cur+nums[j]>sum) continue;
            if(DFS(j,cur+nums[j],sum,nums)) return true;
        }
        return false;
    }
    bool canPartition(vector& nums) {
        if(nums.empty()) return true;
        long sum=0;
        for(int x:nums) sum+=x;
        if(sum%2) return false;
        sum/=2;
        sort(nums.begin(),nums.end());
        if(nums.back()>sum) return false;
        for(int i=nums.size()-1;i>=0;i--){
            if(nums[i]==sum) return true;
            if(DFS(i,nums[i],sum,nums)) return true;
        }
        return false;
    }
};

//2019_08_01
/*
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入: [5,2,6,1]
输出: [2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.

*/
class TNode {
public:
    int val;            // 数字值
    int count;          // 左子树(包括根节点自身)的总节点数,默认是1
    int com;            // 相同值的重复数
    TNode* pLeft;       // 左子节点
    TNode* pRight;      // 右子节点
    TNode(int v)
        : val(v), count(1), com(1), pLeft(nullptr), pRight(nullptr) {}
};

class Solution {
public:
    vector countSmaller(vector& nums) {
        int n = nums.size(); if (n < 1) return vector();
        vector res(n);
        TNode* root = new TNode(nums[n - 1]);
        res[n - 1] = 0;
        for (int i = n - 2; i >= 0; i--) {
            res[i] = createTree(root, nums[i]); // 按原数组从右往左的顺序构建二叉树
        }    
        return res;
    }
    
    // 按原数组从左到右的顺序构建二叉树
    // 函数返回要插入 val 的结果(即其对应的 count 值)
    int createTree(TNode* pCur, int val) {
        if (val > pCur->val) {  // 右转, res += 当前节点的 count 值
            if (pCur->pRight) return createTree(pCur->pRight, val) + pCur->count;
            else {
                pCur->pRight = new TNode(val);
                return pCur->count;
            }
        } else if (val < pCur->val) {   // 左转,当前节点的 count 值 ++
            pCur->count++;
            if (pCur->pLeft) return createTree(pCur->pLeft, val);
            else {
                pCur->pLeft = new TNode(val);
                return 0;
            }
        } else {    // 相等,看作左转,不建立新节点,当前节点的 count 值 ++,注意记录重复值
            return (++pCur->count)- (++pCur->com);
        }
    }
};

有问题可以在下面留言噢!

你可能感兴趣的:(秋招面试)