一、《剑指offer》面试题三中的题目二:不修改数组找出数组中重复的数字
在一个长度为n+1的数组 nums 里的所有数字都在0~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。
例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或3。
作者介绍了二分法来解决这个问题,简单说,就是把1~n从中间的数字m分成两部分,如果前半部分包含的数字超过m,说明该区间内一定有至少一个重复数字,则继续对这个区间进行二分,后面的区间就不用管了。否则,后半部分区间一定包含重复数字,对它继续进行二分和判断。
如此循环,直到区间范围只能容忍一个数字,且该数字的出现次数大于1,则它一定是重复的。
代码如下。
class Solution {
public:
int findRepeatNumber(vector& nums) {
int length = nums.size();
int begin = 0;
int end = length - 1;
while (end >= begin) {
int middle = ((begin + end) >> 2) + begin;
int count = countnum(nums, begin, middle, length); //搜索在这个范围内的数的个数
if (begin == middle) { //如果区间最小的元素与区间最大的元素相等,说明该区间只有一个元素
if (count > 1) //如果这个元素出现次数超过1次说明它重复
return begin;
else //否则,说明这个数组中没有重复元素
return -1;
}
if (count > middle - begin + 1) //如果这个区间的数出现的次数超过了区间范围,说明区间里肯定有某个数重复
end = middle;
else //否则,要遍历紧邻着该区间之后的下一个区间,区间元素的范围是相同的
begin = middle + 1;
}
return -1;
}
int countnum(vector nums, int begin, int end, int length) {
int count = 0;
for(auto a : nums)
if (a <= end && a >= begin)
count++;
return count;
}
};
二、《剑指offer》面试题四:*二维数组中的查找
在一个n*m的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:有矩阵如下。
1 2 8 9
2 4 9 12
4 7 10 13
6 8 11 15
给定查找数字7,则返回true;给定查找数字5,则返回false。
从最右上角的数字开始。如果目标数字小于右上角的数字,则它肯定比这一列的所有数字都小,则不再考虑这一列,查找的范围排除掉这一列,否则不做改变。再查看第一行,如果第一行的倒数第二个数字(倒数第一个数字已经被排除掉了,不再考虑它)小于目标数字,说明这一行所有数字都比目标数字小,则不再考虑第一行,否则不做改变。
第二次循环也同理,让当前的范围内右上角的数字与目标数字比较,缩小行和列的范围。如此循环直到找到数组或找不到数字,返回true或false。
class Solution {
public:
bool findNumberIn2DArray(vector>& matrix, int target) {
int rows = matrix.size(); //数组的行数
if (rows == 0) //说明数组为空
return false;
int cols = matrix[0].size(); //数组的列数
//从右上角开始遍历
int col = cols - 1;
int row = 0;
while (row < rows && col >= 0) {
if (target == matrix[row][col])
return true;
else if (target < matrix[row][col])
col--;
else if (target > matrix[row][col])
row++;
}
return false;
}
};
三、《剑指offer》面试题63:股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
面试的时候真的被问到这个题目了,特此记录一下。首先新建一个数组min。遍历给定的prices数组。
如果数组min为空或prices[i]小于数组的最后一个数字,就把prices[i]也加入到数组min中,因此就得到了一个递减的数列。否则,就说明prices[i]比当前的min最后的数字要大,用prices[i]减去数组min的最后一个数字,也就是在prices[i]之前出现过的最小的数字。
代码如下。
#include
class Solution {
public:
int maxProfit(vector& prices) {
int min[prices.size()];
int max_profit = 0;
int j = 0;
for (int i = 0; i < prices.size(); i++) {
if (j == 0 || prices[i] < min[j - 1]) {
min[j] = prices[i];
j++;
}
else {
int profit = prices[i] - min[j - 1];
max_profit = max_profit > profit ? max_profit : profit;
}
}
return max_profit;
}
};
四、Leetcode332:零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
示例 4:
输入:coins = [1], amount = 1
输出:1
示例 5:
输入:coins = [1], amount = 2
输出:2
经典的动态规划题目,关于动态规划的详细解读,可以参考这篇博客。假设输入coins是{1, 2, 5},金额amount是27。
用表示金额为时所需的最少金币数,则一定有如下的递推关系:。的初始值为,。
最后的代码如下。
#include
#include
class Solution {
public:
int coinChange(vector& coins, int amount) {
int f[amount + 1];
f[0] = 0;
for (int i = 1; i<= amount; i++)
f[i] = INT_MAX;
for (int i = 1; i <= amount; i++)
for (int j = 0; j < coins.size(); j++)
if (coins[j] <= i && f[i - coins[j]] != INT_MAX)
f[i] = min(f[i], f[i - coins[j]] + 1);
if (f[amount] == INT_MAX)
return -1;
return f[amount];
}
};
五、Leetcode62:不同路径问题
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
提示:
1. 1 <= m, n <= 100
2. 题目数据保证答案小于等于 2 * 109
动态规划问题。假设用表示在第i+1行j+1列处(因为是从0开始算的)的不同路径数。
因为走到某个格子要么是从上面的格子向下走到的,要么是从左面的格子向右走到的,所以递推关系式。
初始状态,。
代码如下。
class Solution {
public:
int uniquePaths(int m, int n) {
if (m == 1 || n == 1)
return 1;
int f[m][n];
f[0][0] = 0;
for (int i = 1; i < m; i++)
f[i][0] = 1;
for (int j = 1; j < n; j++)
f[0][j] = 1;
for (int i = 1; i < m; i++)
for (int j = 1; j < n; j++)
f[i][j] = f[i - 1][j] + f[i][j - 1];
return f[m - 1][n - 1];
}
};
六、Leetcode1:两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
第一种方法是暴力求解,使用两层循环,将数组中的每两个数都加一遍直至找到结果。这种方法时间复杂度为O(n^2)。
第二种解法使用哈希表,哈希表中存放已经遍历过的所有元素的值和位置,以值为哈希表的key、位置为value。只需要遍历一次数组,每遍历到一个元素,先查看target减去该元素得到的值是否在哈希表里,如果在就取出它的value,返回结果。否则,就将当前元素加入哈希表中。这种方法以空间换时间,时间复杂度为O(n)。
具体代码如下。
class Solution {
public:
vector twoSum(vector& nums, int target) {
vector result;
map my_map;
map::iterator iter;
for (int i = 0; i < nums.size(); i++) {
iter = my_map.find(target - nums[i]);
if (iter != my_map.end()) {
result.insert(result.end(), iter->second);
result.insert(result.end(), i);
return result;
}
my_map.insert(map::value_type(nums[i], i));
}
return result;
}
};
结果如下。
这篇文章写起来不易,如果它对你有帮助的话,麻烦给个赞吧!!