编程实战一:转置矩阵
思路:因为矩阵转置后不能确保行数与列数与原先相同,故而定义一个二维矩阵B,把A矩阵元素A[i][j]赋值给B[j][i]即可,若相同可以实现原地转置。
vector<vector<int>> transpose(vector<vector<int>>& A) {
int row=A.size(),col=A[0].size();
vector<vector<int>>B(col,vector<int>(row,0));
for(int i=0;i<row;i++)
for(int j=0;j<col;j++)
{
B[j][i]=A[i][j];
}
return B;
}
编程实战二:寻找众数
题目描述:数组中占比超过一半的元素称之为主要元素。给定一个整数数组,找到它的主要元素。若没有,返回-1。
思路:摩尔投票,设置一个计算次数的变量cnt=0,遍历数组元素nums[i],如果cnt为0,那么major=nums[i],cnt=1否则的话,cnt++,不相等,cnt–。最后如果cnt>0,遍历数组验证major是否为众数,否则没有众数返回-1
int majorityElement(vector<int>& nums) {
int cnt=0,major;
for(auto&i:nums)
{
if(cnt==0)
{
major=i;
cnt=1;
}
else
{
if(major!=i)
{
cnt--;
}
else
{
cnt++;
}
}
}
if(cnt>0)
{
cnt=0;
for(auto&i:nums)
{
if(major==i)
{
cnt++;
}
}
if(cnt>nums.size()/2)
return major;
}
return -1;
}
编程实战三:有序数组平方
题目:给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]
思路:观察数组特点发现非递减顺序,因此平方后会出现左半边递减,右半边递增,可以用双指针,分别从头尾0和n-1开始遍历,判断哪个数大,就把大的数放到res[k]中,当k从n-1至0中。
vector<int> sortedSquares(vector<int>& nums) {
vector<int>res(nums.size(),0);
int i=0,j=nums.size()-1,k;
for(k=0;k<nums.size();k++)
nums[k]=nums[k]*nums[k];
k=j;
while(i<j)
{
if(nums[i]>nums[j])
{
res[k--]=nums[i++];
}
else
{
res[k--]=nums[j--];
}
}
res[k]=nums[i];
return res;
}
编程实战四:所有奇数长度子数组之和
题目:给你一个正整数数组 arr ,请你计算所有可能的奇数长度子数组的和。
示例:
输入:arr = [1,4,2,5,3]
输出:58
解释:所有奇数长度子数组和它们的和为:
[1] = 1
[4] = 4
[2] = 2
[5] = 5
[3] = 3
[1,4,2] = 7
[4,2,5] = 11
[2,5,3] = 10
[1,4,2,5,3] = 15
我们将所有值求和得到 1 + 4 + 2 + 5 + 3 + 7 + 11 + 10 + 15 = 58
题解一:暴力求解,从第一个数开始遍历,依次把子数组长度从1开始延伸,每次跨度加2,长度i+size依次就是1,3,5,7…直到大于n为止。
int sumOddLengthSubarrays(vector<int>& arr) {
int res = 0;
for(int i = 0; i < arr.size(); i ++)
for(int sz = 1; i + sz - 1 < arr.size(); sz += 2)
res += accumulate(arr.begin() + i, arr.begin() + i + sz, 0);
return res;
题解二:其实可以遍历数组一次就能获得结果,那就是考虑数组中每一个元素在子数组中出现的次数times[i],然后把每一个arr[i]*times[i]的结果累加起来就是最终答案。问题现在就变成了考虑每一个元素出现的次数,对于arr[i]来说,左边可选择0,1,2,3…个数,右边可选择0,1,2,3…个数,所以left=i+1种可能,right=n-i种可能,比如i=0时,左边可以选择0个数,所以只有一种可能,left=1,右边同理。接下来计算左边有多少种奇数情况,多少种偶数情况,右边多少种奇数,右边多少种偶数。原因在于当一个数已经确定后,要以这个数构成奇数长度子数组,无非两种情况,1:左边取偶数个,右边取偶数个,总的个数一定为奇数。2.左边取奇数个,右边取奇数个,总的个数一定为奇数。
int sumOddLengthSubarrays(vector<int>& arr) {
int res = 0;
for(int i = 0; i < arr.size(); i ++){
int left = i + 1, right = arr.size() - i,
left_even = (left + 1) / 2, right_even = (right + 1) / 2;//偶数情况
left_odd = left / 2, right_odd = right / 2;//奇数情况
res += (left_even * right_even + left_odd * right_odd) * arr[i];
}
return res;
}
编程实战五:重复的数II
题目:给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
思路:用一个哈希表,遍历数组过程中顺便记录其下标,一旦出现重复,就比较下标,满足条件返回true,否则更新下标
bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_map<int,int>mp;
for(int i=0;i<nums.size();i++){
if(!mp[nums[i]]){
mp[nums[i]]=i+1;
}
else{
if(i-mp[nums[i]]+1<=k)return true;
else mp[nums[i]]=i+1;
}
}
return false;
}
编程实战六:汇总区间
题目:给定一个无重复元素的有序整数数组 nums 。返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。列表中的每个区间范围 [a,b] 应该按如下格式输出:
“a->b” ,如果 a != b
“a” ,如果 a == b
示例:
输入:nums = [0,1,2,4,5,7]
输出:["0->2","4->5","7"]
解释:区间范围是:
[0,2] --> "0->2"
[4,5] --> "4->5"
[7,7] --> "7"
思路:准备一个字符串变量str,用一个start和end用来标记起始数字和终止数字,如果是逐一递增就更新end值,否则说明start和end可以储存在str中,放入vector中了,此时注意会出现两种情况,第一种,start与end值相等,第二种start与end不相等。处理完后就可以更新start,end以及str。当所有元素遍历完毕后要注意此时还有最后一段数据也得存放到vector里面,同样分两种情况,就不再赘述。
vector<string> summaryRanges(vector<int>& nums) {
vector<string> res;
if(nums.size()==0)
{
return res;
}
string str="";
int start=-666,end=start;
for(int i=0;i<nums.size();i++)
{
if(start==-666)
{
start=nums[i];
end=start;
}
else
{
if(nums[i]-1==end)
{
end=nums[i];
}
else
{
if(start!=end)
{
str=to_string(start)+"->"+to_string(end);
}
else{
str=to_string(start);
}
res.push_back(str);
str="";
start=-666;
i--;
}
}
}
if(start!=-666&&start!=end)
{
str=to_string(start)+"->"+to_string(end);
}
else if(start!=-666&&start==end)
{
str=to_string(start);
}
res.push_back(str);
return res;
}
编程实战七:种花问题
题目:假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。
示例 1:
输入: flowerbed = [1,0,0,0,1], n = 1
输出: True
思路:遍历数组,只要flowerbed[i]为0,并且左边右边都为0,那么这个位置可以种花,count++,最后判断count是否大于n
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int count = 0;
for(int i = 1; i < flowerbed.size()-1; i++)
{
if (flowerbed[i] == 0 && (i == 0 || flowerbed[i - 1] == 0)&&(i==flowerbed.length - 1 || flowerbed[i + 1] == 0))
{
flowerbed[i] = 1;
count++;
}
}
return n <= count;
}
编程实战八:反转图像
题目:给定一个二进制矩阵 A,我们想先水平翻转图像,然后反转图像并返回结果。水平翻转图片就是将图片的每一行都进行翻转,即逆序。例如,水平翻转 [1, 1, 0] 的结果是 [0, 1, 1]。反转图片的意思是图片中的 0 全部被 1 替换, 1 全部被 0 替换。例如,反转 [0, 1, 1] 的结果是 [1, 0, 0]。
思路:直接暴力模拟就行了
vector<vector<int>> flipAndInvertImage(vector<vector<int>>& A) {
for(auto &i:A)
for(int j=0,temp=0,n=i.size();j<(n+1)/2;++j)
{
temp=i[j]^1;
i[j]=i[n-j-1]^1;
i[n-j-1]=temp;
}
编程实战九:找出井字棋获胜者
题目:A 和 B 在一个 3 x 3 的网格上玩井字棋。井字棋游戏的规则如下:
玩家轮流将棋子放在空方格 (" ") 上。
第一个玩家 A 总是用 “X” 作为棋子,而第二个玩家 B 总是用 “O” 作为棋子。
“X” 和 “O” 只能放在空方格中,而不能放在已经被占用的方格上。
只要有 3 个相同的(非空)棋子排成一条直线(行、列、对角线)时,游戏结束。
如果所有方块都放满棋子(不为空),游戏也会结束。
游戏结束后,棋子无法再进行任何移动。
给你一个数组 moves,其中每个元素是大小为 2 的另一个数组(元素分别对应网格的行和列),它按照 A 和 B 的行动顺序(先 A 后 B)记录了两人各自的棋子位置。
如果游戏存在获胜者(A 或 B),就返回该游戏的获胜者;如果游戏以平局结束,则返回 “Draw”;如果仍会有行动(游戏未结束),则返回 “Pending”。
你可以假设 moves 都 有效(遵循井字棋规则),网格最初是空的,A 将先行动。
示例:
输入:moves = [[0,0],[2,0],[1,1],[2,1],[2,2]]
输出:"A"
解释:"A" 获胜,他总是先走。
"X " "X " "X " "X " "X "
" " -> " " -> " X " -> " X " -> " X "
" " "O " "O " "OO " "OOX"
思路:考虑到井字棋只有八种赢的可能性,所以可以用一个二维数组保存每一种获胜可能,把九个空格编号0,1,2,3,4,5,6,7,8,然后遍历moves数组把A和B走的路径分别保存下来,最后与所有的获胜可能比较,只要相等,就能知道谁获胜了,如果两个人都没赢,就是平局或者未结束了。
优化:按照原有思路,其实可以只考虑最后一个落子的是谁就可以知道了,比如A最后落子,那么B绝对不可能获胜,因此可以不必考虑B的路径,只需要考虑A的路径,同理B最后落子情况类似
class Solution {
public:
bool checkwin(unordered_set<int>& S, vector<vector<int>>& wins) {
for (auto win: wins) {
bool flag = true;
for (auto pos: win) {
if (!S.count(pos)) {
flag = false;
break;
}
}
if (flag) {
return true;
}
}
return false;
}
string tictactoe(vector<vector<int>>& moves) {
vector<vector<int>> wins = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6}
};
unordered_set<int> A, B;
for (int i = 0; i < moves.size(); ++i) {
int pos = moves[i][0] * 3 + moves[i][1];
if (i % 2 == 0) {
A.insert(pos);
}
else {
B.insert(pos);
}
}
if (checkwin(A, wins)) {
return "A";
}
if (checkwin(B, wins)) {
return "B";
}
return (moves.size() == 9 ? "Draw" : "Pending");
}
};
编程实战十:两数之和
题目:给出一个整数数组,请在数组中找出两个加起来等于目标值的数,
你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2.。注意:下标是从1开始的
假设给出的数组中只存在唯一解
例如:
给出的数组为 {20, 70, 110, 150},目标值为90
输出 index1=1, index2=2
思路:用哈希表保存遍历过的元素,key为元素值,value为index(注意题目中的下标含义),由于是两数之和,例如a和b,假如数组中存在a+b==target,那么,那么肯定a和b有先后,先找到的a或者b肯定已经在哈希表当中了。所以根据这个特点就可以写出代码了。
vector<int> twoSum(vector<int>& numbers, int target) {
// write code here
unordered_map<int,int> ss;
vector<int> res(2);
for(int i=0;i<numbers.size();i++)
{
auto iter=ss.find(target-numbers[i]);
if(iter!=ss.end()&&iter->second!=i+1)
{
res[0]=iter->second;
res[1]=i+1;
break;
}
else
ss[numbers[i]]=i+1;
}
return res;
}
编程实战十一:买卖股票最好时机
题目描述
假设你有一个数组,其中第\ i i 个元素是股票在第\ i i 天的价格。
你有一次买入和卖出的机会。(只有买入了股票以后才能卖出)。请你设计一个算法来计算可以获得的最大收益。
思路:由于只有一次买入和卖入,所以可以直接暴力求解,双重for循环,时间复杂度为O(n2),这里主要想给出一个优化方案,时间复杂度优化至O(n),一次遍历,min为第一个元素,profit表示收益,如果遇到元素比min大,更新收益profit,否则,更新min值
int maxProfit(vector<int>& prices) {
// write code here
if(prices.size()<2)
{
return 0;
}
int min=prices[0],profit=0;
for(int i=1;i<prices.size();i++)
{
if(prices[i]>min)
{
profit=prices[i]-min>profit?prices[i]-min:profit;
}
else
min=prices[i];
}
return profit;
}