文章目录
面试经典150题
Leetcode169 多数元素
数组排序法:直接将数组排序,因为多数元素出现的次数大于n/2,所以在低n/2个位置一定是结果。
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
摩尔投票法:核心理念是票数的正负抵消,时间和空间复杂度分别为O(N)
和O(1)
,最佳解法。
摩尔投票解决问题:如果在任意多的候选人中(选票无序),选出获得票数最多的那个。
算法分为两个阶段:
- 对抗阶段:分属两个候选人的票数进行两两对抗抵消。
- 计数阶段:计算对抗结果中最后留下的候选人票数是否有效。
遍历投票数组,将当前票数最多的候选人与其获得的(抵消后)票数分别存储在candidate
和count
中。
count==0
代表candidate
空缺,直接使用当前数作为候选人,并令count++
count!=0
代表当前candidate
的票数还没有被完全抵消,count--
public static int majority(int[] nums){
int candidate = 0, count = 0;
for(int i = 0; i < nums.length; i++){
if(count == 0){
candidate = nums[i];
count++;
}
else {
if(candidate == nums[i]){
count++;
}
else {
count--;
}
}
}
return candidate;
}
题目说明“给定数组总是存在多数元素”,故不需要考虑数组不存在众数的情况。否则需要加上验证环节,遍历一遍数组,统计candidate
的数量是否超过数组长度的一半。
摩尔投票法升级:
- 如果至多选一个代表,那么他的票数至少要超过
1/2
的票数 - 如果至多选两个代表,那么他的票数至少要超过
1/3
的票数 - 如果至多选m个代表,那么他的票数至少要超过
1/(m+1)
的票数
Leetcode189 轮转数组
- 首先对整个数组进行反转,这样原来数组中需要反转的子数组就会跑到最前面。
- 选择前k个元素作为一个子数组进行反转,再将剩余的n-k个数组进行反转
public static void rotate(int[] nums, int k){
k = k % nums.length;
// 整体反转
rotateArrays(nums, 0, nums.length-1);
// 划分:[0,k-1],[k,nums.length-1]
// 各自反转
rotateArrays(nums, 0, k-1);
rotateArrays(nums, k, nums.length-1);
}
public static void rotateArrays(int[] nums, int left, int right){
while (left < right){
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
Leetcode121 买卖股票的最佳时机
使用minPrice
维护前面i
天的最小值,作为买入价格。由于只能买卖一次,在遍历过程中,维护prices[i]-minPrice
的最大值。
public static int maxProfit(int[] prices){
int minPrice = prices[0];
int maxValue = 0;
for(int i = 1; i < prices.length; i++){
minPrice = Math.min(minPrice, prices[i]);
maxValue = Math.max(maxValue, prices[i] - minPrice);
}
return maxValue;
}
Leetcode122 买卖股票的最佳时机Ⅱ
动态规划:
dp[i][0]
表示下标为i
的这一天,持股状态为买入状态,手上拥有的最大现金数。dp[i][1]
表示下标为i
的这一天,持股状态为卖出状态,手上拥有的最大现金数。
状态转移过程:
- 状态从持有现金开始,到最后一天仍然是持有现金状态(卖出股票的状态)
- 每一天状态可以转移,也可以不同。
由于不限制交易次数,除了最后一天,每一天的状态可能不变化,也可能转移。
确定初始值:
- 起始这一天持有股票,
dp[0][0]=-prices[i]
- 起始这一天什么都不做,
dp[0][1]=0
public static int maxProfit(int[] prices){
int[][] dp = new int[prices.length][2];
// dp[i][0]:第i天买入状态
// dp[i][1]:第i天卖出状态
dp[0][0] = -prices[0];
for(int i = 1; i < prices.length; i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
}
return Math.max(dp[prices.length-1][0], dp[prices.length-1][1]);
}
Leetcode055 跳跃游戏
遍历数组,记录当前位置能够到达的最远位置,并更新最远到达位置rightBoundary
。
当前遍历元素下标应该小于等于rightBoundary
且小于数组长度。
最后判断rightBoundary
是否大于等于nums.length - 1
.
public static boolean canJump(int[] nums){
int rightBoundary = 0;
int pos = 0;
while (pos < nums.length && pos <= rightBoundary){
rightBoundary = Math.max(rightBoundary, pos + nums[pos]);
pos++;
}
return rightBoundary >= nums.length - 1;
}
Leetcode045 跳跃游戏Ⅱ
贪心:每次在可以到达的范围内选择可以使得跳的更远的位置
如下,开始位置最远可以跳两个单位,可到达范围为橙色。在下一条中,先到达能够跳3个单位的节点,能够保证到达更远的位置。
每一步都是局部最优,最后实现全局最优。
public static int jump(int[] nums){
int step = 0, cover = 0, end = 0;
for(int i = 0; i < nums.length; i++){
// 下一条能够覆盖的最大边界,通过访问本条能够到达的范围进行更新
cover = Math.max(cover, nums[i] + i);
if(i == end){
// 更新边界,增加步数
end = cover;
step++;
}
}
return step;
}
Leetcode274 H指数
设n为研究者发表的论文数量。h不可能超过n,多以对于引用次数大于n的论文,在统计的时候可以看成引用次数等于n的论文。
创建一个长为n+1
的cnt
数组,统计min(citations[i],n)
的出现次数。
设s
为引用次数>=i
的论文数量,需要算出满足s>=i
的最大的i
为了计算多少论文的引用次数>=i
,可以从i=n
开始倒序循环每次循环,把cnt[i]
加入到s
中。当s>=i
时,此时的i
就是满足s>=i
的最大的i
。
public static int hIndex(int[] citations){
int n = citations.length;
// count[i]表示引用次数为i的论文数量
int[] count = new int[n+1];
for(int c : citations){
count[Math.min(c, n)]++;
}
// 引用次数大于等于i的论文数量
int s = 0;
// 从后向前遍历
for(int i = n; i >= 1; i--){
s += count[i];
if(s >= i){
return i;
}
}
return 0;
}
Leetcode380 O(1)时间插入、删除和获取随机元素
对于insert
和remove
操作使用哈希表实现O(1)
时间复杂度。对于getRandom
操作,在一个数组内使用随机下标进行返回。
将哈希表设计为:使用value
作为键值,使用数组下标loc
作为值。
申请一个足够大的数组nums
(根据题目要求,数据范围为2*105),使用变量idx
记录当前使用到哪一位(即下标在[0,idx]
范围内均存在值)
insert
操作:使用哈希表判断value
是否存在,存在的话返回false
,否则将其添加到nums
中,并更新idx
,同时更新哈希表。remove
操作:使用哈希表判断值value
是否存在,不存在返回false
,否则将value
从哈希表中删除,同时取出在nums
中的下标loc
,然后将nums[idx]
赋值到loc
位置,并更新idx
(表示将原本处于loc
位置的元素删除),同时更新原本位于idx
位置的数在哈希表中的值为loc
(若loc
和idx
相等,说明删除的是最后一个元素,这一步可以跳过)getRandom
操作:确保了[0,idx]
均为存活值,因此可以直接在[0,idx+1]
范围内进行随机即可。
public A0423RandomizedSet(){
}
private static int[] nums = new int[200001];
Map<Integer, Integer> map = new HashMap<>();
int idx = -1;
Random random = new Random();
public boolean insert(int val){
if(map.containsKey(val)){
return false;
}
// 插入
map.put(val, ++idx);
nums[idx] = val;
return true;
}
public boolean remove(int val){
if(!map.containsKey(val)){
return false;
}
// val在nums数组中存放的下标
int loc = map.remove(val);
if(idx == loc){
// 删除的最后一个元素
idx--;
return true;
}
// 删除数组中的中间元素
// TODO:将数组最后元素赋值到删除元素在数组中的位置,同时更改map中val对应的loc
nums[loc] = nums[idx];
idx--;
map.put(nums[loc], loc);
return true;
}
public int getRandom(){
return nums[random.nextInt(idx+1)];
}
Leetcode238 除自身以外数组的乘积
题目保证数组nums
之中的任意元素的全部前缀元素和后缀的乘积都在32位整数范围内。
要求:不要使用除法,且在O(n)时间复杂度内完成此题
原数组 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
前缀 | 1 | 2 | 2 * 3 | 2 * 3 * 4 |
后缀 | 3 * 4 * 5 | 4 * 5 | 5 | 1 |
结果 | 60 | 40 | 30 | 24 |
public static int[] productExceptSelf(int[] nums){
int[] res = new int[nums.length];
int tmp = 1;
res[0] = tmp;
for(int i = 1; i < nums.length; i++){
tmp *= nums[i-1];
res[i] = tmp;
}
tmp = 1;
for (int i = nums.length - 2; i >= 0 ; i--) {
tmp *= nums[i+1];
res[i] *= tmp;
}
return res;
}