合并两个数组
题目说明:
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。 示例 1: 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6] 解释:需要合并 [1,2,3] 和 [2,5,6] 。 合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。 示例 2: 输入:nums1 = [1], m = 1, nums2 = [], n = 0 输出:[1] 解释:需要合并 [1] 和 [] 。 合并结果是 [1] 。 示例 3: 输入:nums1 = [0], m = 0, nums2 = [1], n = 1 输出:[1] 解释:需要合并的数组是 [] 和 [1] 。 合并结果是 [1] 。 注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
方法一:
/*方法一*/
public void merge(int[] nums1, int m, int[] nums2, int n) {
int j = 0;
int i = n;
int temp = 0;
//首先判断n的长度,及nums2的长度是否为0,是的话就不需要移位
if(n!=0){
//将原始nums1的元素向后移动n位
for (int k = m - 1; k >= 0; k--) {
nums1[n + k] = nums1[k];
nums1[k] = 0;
}
}
//将num1s于num2s数组的元素两两比较
while (j < n && i < m + n) {
if (nums2[j] < nums1[i]) {
nums1[temp] = nums2[j];
j++;
} else {
nums1[temp] = nums1[i];
i++;
}
temp++;
}
//判断num2中是否还有元素未添加到num1s中
if (j < n) {
for (int k = j; k < n; k++) {
nums1[temp] = nums2[k];
temp++;
}
}
System.out.println(Arrays.toString(nums1));
}
方法二:
/*方法二*/
public void merge2(int[] nums1, int m, int[] nums2, int n) {
//将两个数组进行合并
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
//利用JavaAPI对数组进行自动排序
Arrays.sort(nums1);
}
方法三:
/*第三种方法
使用双指针方法,将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中
* */
public void merge三(int[] nums1, int m, int[] nums2, int n) {
//将nums1,nums2看作是两个链表,用p1,p2分别指向两个链表的第一元素
int p1 = 0, p2 = 0;
//新建一个临时数组,用来存放排序完的元素
int[] sorted = new int[m + n];
//中间变量
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
//将排序好的元素,添加到临时数组中
sorted[p1 + p2 - 1] = cur;
}
//将临时数组赋值给nums1
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
方法四:
/*方法四
逆向双指针,指针设置为从后向前遍历,每次取两者之中较大者放进nums1的最后面
* */
public void merge四(int[] nums1, int m, int[] nums2, int n) {
//将nums1,nums2看作是两个链表,用p1,p2分别指向两个链表的最后一个元素
int p1 = m - 1, p2 = n - 1;
//指向合nums1链表的最后一个元素
int tail = m + n - 1;
//中间变量
int cur;
while (p1 >= 0 || p2 >= 0) {
if (p1 == -1) {
cur = nums2[p2--];
} else if (p2 == -1) {
cur = nums1[p1--];
} else if (nums1[p1] > nums2[p2]) {
cur = nums1[p1--];
} else {
cur = nums2[p2--];
}
nums1[tail--] = cur;
}
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 说明: 为什么返回数值是整数,但输出的答案是数组呢? 请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 你可以想象内部操作如下: // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝 int len = removeElement(nums, val); // 在函数里修改输入数组对于调用者是可见的。 // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。 for (int i = 0; i < len; i++) { print(nums[i]); } 示例 1: 输入:nums = [3,2,2,3], val = 3 输出:2, nums = [2,2] 解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。 示例 2: 输入:nums = [0,1,2,2,3,0,4,2], val = 2 输出:5, nums = [0,1,4,0,3] 解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
方法一:
/*第一种方法*/
public int removeElement(int[] nums, int val) {
//创建一个集合,用来存放不等于val的值
List<Integer> list=new ArrayList<>();
//遍历数组,将不等于val的元素存放到集合中
for (int i = 0; i < nums.length; i++) {
if(nums[i]!=val){
list.add(nums[i]);
}
}
//将list集合中的元素赋值到sums数组中
for (int i = 0; i < list.size(); i++) {
nums[i]=list.get(i);
}
System.out.println(Arrays.toString(nums));
return list.size();
}
方法二:
/*第二种方法
使用双指针
* */
public int removeElement2(int[] nums, int val) {
//指向第一个元素的指针
int left=0;
//指向第二个元素的指针
int right=nums.length-1;
while (left<=right){
if(nums[left]==val){
nums[left]=nums[right--];
}
else {
left++;
}
}
System.out.println(Arrays.toString(nums));
System.out.println(left);
//由于left一开始的初始值是0,如果用left作为数组的长度的话,要加1
return left++ ;
}
方法三:
/*解法三 通用解法
* */
public int removeElement3(int []nums,int val){
//作用计算数组长度,以及重新给数组赋值
int i=0;
for (int x: nums) {
if(x!=val) nums[i++]=x;
}
System.out.println(Arrays.toString(nums));
System.out.println(i++);
return i++;
}
删除有序数组的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过: 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。 返回 k 。 判题标准: 系统会用下面的代码来测试你的题解: int[] nums = [...]; // 输入数组 int[] expectedNums = [...]; // 长度正确的期望答案 int k = removeDuplicates(nums); // 调用 assert k == expectedNums.length; for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i]; } 如果所有断言都通过,那么您的题解将被 通过。 示例 1: 输入:nums = [1,1,2] 输出:2, nums = [1,2,_] 解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。 示例 2: 输入:nums = [0,0,1,1,1,2,2,3,3,4] 输出:5, nums = [0,1,2,3,4] 解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
方法一:
/*第一种方法*/
public int removeDuplicates(int[] nums) {
//指向nums第二个元素
int i=1;
//指向list的第一个元素
int j=0;
List<Integer> list=new ArrayList<>();
//将nums第一个元素添加到集合中
list.add(nums[0]);
//筛选不重复元素,添加到list集合中
while (i<nums.length){
if(list.get(j)!=nums[i]){
list.add(nums[i]);
j++;
}
i++;
}
//将不重复的元素,赋值到nums中
for (int k = 0; k < list.size(); k++) {
nums[k]=list.get(k);
}
System.out.println(Arrays.toString(nums));
System.out.println(list.size());
return list.size();
方法二:
/*第二种方法*/
public int removeDuplicates2(int[] nums){
//指向nums的第一元素
int left=0;
//指向nums的第二个元素
int right=1;
/*判断是否是空数组*/
if(nums.length==0){
return 1;
}
/*筛选非重复元素*/
while (right<nums.length){
if(nums[left]!=nums[right]){
nums[++left]=nums[right++];
}
else {
right++;
}
}
//因为left是从0开始,数组长度从1开始,所以要加一
left++;
System.out.println(left);
System.out.println(Arrays.toString(nums));
return left;
}
删除有序数组重复项||
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 示例 1: 输入:nums = [1,1,1,2,2,3] 输出:5, nums = [1,1,2,2,3] 解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。 示例 2: 输入:nums = [0,0,1,1,1,1,2,3,3] 输出:7, nums = [0,0,1,1,2,3,3] 解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。
方法一:
public int removeDuplicates(int[] nums) {
//指向nums的第一个元素
int left=0;
//指向nums的第二个元素
int right=1;
//设置中间变量,判断重复值是否小于等于2,如果temp等于1,证明重复值个数大于2
int temp=0;
//判断nums数组是否为空或者为1
if(nums.length==0||nums.length==1){
return nums.length;
}
//进行元素比较,筛选条件合适的元素
while (right<nums.length) {
if(nums[left]==nums[right]&&temp==0){
nums[++left] = nums[right++];
temp=1;
}
else if (nums[left] != nums[right] ) {
nums[++left] = nums[right++];
temp=0;
}
else {
right++;
}
}
System.out.println(left);
System.out.println(Arrays.toString(nums));
return ++left;
}
方法二:
/*双指针优化*/
public int removeDuplicates2(int[] nums){
//指向nums元素的第三个元素
int slow=2;
int fast=2;
//判断nums的长度是否小于等于2
if(nums.length<=2){
return nums.length;
}
//筛选适合的元素
while (fast<nums.length){
if(nums[slow-2]!=nums[fast]){
nums[slow]=nums[fast];
slow++;
}
fast++;
}
//因为执行到最后,slow的值会比原来的数组长度多一个,假设nums[slow]代表符合元素的最后一个元素,此时slow的值就是数组的长度,但在执行过程中
// slow要加1,确保每个有效元素都被遍历到
slow--;
System.out.println(Arrays.toString(nums));
System.out.println(slow);
return slow;
}
多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 n/2 (向上取整Math.ceil())的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入:nums = [3,2,3] 输出:3 示例 2: 输入:nums = [2,2,1,1,1,2,2] 输出:2
方法一:
/*第一种方式*/
public int majorityElement(int[] nums) {
//存放结果值
int result = 0;
//存放数组长度一半的值
int n = 0;
//如果数组长度等于1,则直接返回
if (nums.length == 1) {
return nums[0];
}
//如果数组长度为奇数,nums.length/2向上取整
if (nums.length % 2 == 1) {
n = nums.length / 2 + 1;
} else {
n = nums.length / 2;
}
//建立一个hashMap key存储元素,value存储元素在数组中的个数
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; i++) {
if (!map.containsKey(nums[i])) {
map.put(nums[i], 1);
} else {
map.put(nums[i], map.get(nums[i]) + 1);
if (map.get(nums[i]) >= n) {
result = nums[i];
break;
}
}
}
System.out.println(result);
return result;
}
方法二:
/*第二种方式
如果将数组nums中的的所有元素按照单调递增或单调递减的顺序排序,那么下标[n/2-1]的元素(下表从0开始)一定是众数
* */
public int majorityElement2(int [] nums){
Arrays.sort(nums);
System.out.println(nums[nums.length / 2-1]);
return nums[nums.length / 2-1];
}
方法三:
/*方法三
摩尔投票算法
设输入数组nums的众数为x,数组长度为n
推论一:若记众数的票数为+1,非众数的票数为减一,则一定所有的票数合大于零
推论二:若数组的前a个数字的票数和=0,则数组剩(n-a)个数字的票数和一定仍>0,即后(n-a)个数字的众数仍为x
根据以上推论,记数组首个元素为n1,众数为x,遍历并统计票数,当发生票数和=0时,剩余数组的众数一定不变,这是由于:
当n1=x:抵消的所有数字中,有一半时众数
当n1不等于x:抵消得所有数字中,众数x的数量最少为0个,最多为一半
利用此特性,每轮假设发生票数和=0都可以缩小剩余数组区间,当遍历完成时,最后一轮假设的数字即为众数。
* */
public int majorityElement3(int [] nums){
int x=0;
int count=0;
int i=0;
while (i<nums.length){
if(count==0){
x=nums[i];
}
count=x==nums[i]?count+1:count-1;
i++;
}
System.out.println(x);
return x;
}
轮转数组
轮转数组 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4] 示例 2: 输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100] 解释: 向右轮转 1 步: [99,-1,-100,3] 向右轮转 2 步: [3,99,-1,-100]
方法一:
//方法一
public void rotate(int[] nums, int k) {
//判断移动的长度是不是大于等于数组的长度
if(k>=nums.length){
//判断移动的长度时数组长度的倍数是奇数还是偶数,奇数的话,数组反转,偶数的话数组不变
if((k/ nums.length)%2==1){
nums=reverse(nums);
//判断移动的长度是否恰好等于数组长度的倍数,不是的话移动k%nums.length位
if(k%nums.length!=0){
int n=k%nums.length;
nums=move(n,nums);
}
}
//偶数不反转
else {
if((k%nums.length!=0)){
//判断移动的长度是否恰好等于数组长度的倍数,不是的话移动k%nums.length位
int n=k%nums.length;
nums=move(n,nums);
}
}
}
//移动位数小于数组长度的情况
else {
nums=move(k,nums);
}
}
// 此方法用来进行数组移动
public int [] move(int k,int [] nums){
int [] tempArray=new int[k];
for (int i = 0; i < k; i++) {
tempArray[i]=nums[nums.length-k+i];
}
for (int i = nums.length-k-1; i >=0; i--) {
nums[i+k]=nums[i];
}
for (int i = 0; i < k; i++) {
nums[i]=tempArray[i];
}
return nums;
}
//此方法用来数组倒转
public int[] reverse(int [] nums){
int left=0;
int right=nums.length-1;
int temp;
while (left>right){
temp=nums[left];
nums[left]=nums[right];
nums[right]=temp;
left++;
right--;
}
return nums;
}
方法一优化:
//代码优化
public void rotate2 (int[] nums,int k){
int n=k%nums.length;
move(n,nums);
}
// 此方法用来进行数组移动
public int [] move(int k,int [] nums){
int [] tempArray=new int[k];
for (int i = 0; i < k; i++) {
tempArray[i]=nums[nums.length-k+i];
}
for (int i = nums.length-k-1; i >=0; i--) {
nums[i+k]=nums[i];
}
for (int i = 0; i < k; i++) {
nums[i]=tempArray[i];
}
return nums;
}
方法二:
/*方法二*/
public void rotate3(int[] nums,int k){
int [] tempArray=new int[nums.length];
for (int i = 0; i < nums.length; i++) {
tempArray[(i+k)% nums.length]=nums[i];
}
System.arraycopy(tempArray,0,nums,0,nums.length);
}
买卖股票的最佳时机
最大利润 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 示例 1: 输入:[7,1,5,3,6,4] 输出:5 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 示例 2: 输入:prices = [7,6,4,3,1] 输出:0 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
方法一:
/*第一种 暴力破解 超出时间限制*/
public int maxProfit(int[] prices) {
int max=0;
for (int i = 0; i < prices.length; i++) {
for (int j = i+1; j < prices.length; j++) {
int middle=prices[j]-prices[i];
if(middle>max){
max=middle;
}
}
}
if(max<=0){
return 0;
}
return max;
}
方法二:
/*一次遍历
*
* 第二种解析其实是一种动态的变化,在遍历向前推进时,找到一个最小买入价格minprice,然后,在没有找到下一个更小的买入价格时,
* 计算接下来每一天的利润,记录其中最大利润。如果找到下一个最小买入价格minprice,继续计算接下来未找到下一个更小买入价格时的利润最大值,
* 直到遍历完prices数组,maxProfit就是历史最大差值!
*
* */
public int maxProfit2(int []prices){
int minPrice=Integer.MAX_VALUE;
int maxProfit=0;
for (int i = 0; i < prices.length; i++) {
if(prices[i]<minPrice){
minPrice=prices[i];
}
else if(prices[i]-minPrice>maxProfit){
maxProfit=prices[i]-minPrice;
}
}
return maxProfit;
}
买卖股票的最佳时机||
买卖股票时机|| 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。 返回 你能获得的 最大 利润 。 示例 1: 输入:prices = [7,1,5,3,6,4] 输出:7 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。 总利润为 4 + 3 = 7 。 示例 2: 输入:prices = [1,2,3,4,5] 输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。 总利润为 4 。 示例 3: 输入:prices = [7,6,4,3,1] 输出:0 解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。
方法一:
/*一次遍历
获取每次最低的股票价格进行购买,然后看第二天股票是否比前一天高,是的话,股票卖出,然后获取的利润
,不是的话,第二天作为最低股票
然后重复之前操作。
这种方式,只要每天比第二天价格低,就出售,然后每次利润与之前获取的利润相叠加,就是最大利润,这种股票依次递增情况下,也就是整个股票是依次递增的,获取的利润与,
整个股票历史天数最低,和整个股票历史天数最高的之间的差相等。因此就不必考虑历史最低股票,与历史最高股票问题。只要考虑
有利润就出售。
属于贪心算法
* */
public int maxProfit(int[] prices) {
//表示最低股票买入
int minPrice=Integer.MAX_VALUE;
//最大利润
int maxProfit=0;
for (int i = 0; i < prices.length; i++) {
//判断第二天的股票价格是否比前一天低,使得话,第二天为最低股票价格
if(prices[i]<minPrice){
minPrice=prices[i];
}
//不是的话,出售第二天股票,与之间获取的利润相叠加
else {
maxProfit=maxProfit+prices[i]-minPrice;
minPrice=prices[i];
}
}
return maxProfit;
}
方法二:
/*动态规划
实际是获取每一天的最大利润,然后相互叠加。
* */
public int maxProfit2(int[] prices){
int n =prices.length;
int [][] dp=new int[n][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for (int i = 1; i < n; 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 dp[n-1][0];
}
跳跃游戏
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 示例 1: 输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 示例 2: 输入:nums = [3,2,1,0,4] 输出:false 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
方法一:
/*
使用贪心算法
每次寻找能到达右边的最大距离
* */
public boolean canJump(int[] nums) {
//能到达右边的最大距离
int rightMost=0;
//遍历所有元素,找到每次
for (int i = 0; i < nums.length; i++) {
if(i<=rightMost){
rightMost=Math.max(rightMost,i+nums[i]);
if(rightMost>=nums.length-1){
return true;
}
}
}
return false;
}
方法二:
/*
第二种 方法 回溯法
将所有可能的跳跃的路径都找到,只要有一条路径可以走的通,就算成功
* */
public boolean canJump2(int [] nums){
//开始跳跃的起始位置
int position=0;
//起始位置的元素值
int step=nums[0];
//存放能走同的路径,1代表可以走通
List<Integer> list=new ArrayList<>();
//该函数判读是否可以走通
isArrive(nums,position,step,list);
return list.contains(1);
}
public void isArrive(int[] nums,int position,int step,List<Integer> list){
//如果当前位置+跳跃的距离大于等于数组长度减一,则在集合中存入1,代表有跳跃路径可以跳到最后一个元素位置
if(position+step>=nums.length-1){
list.add(1);
}
else {
//遍历每一元素,的每一个跳跃路径
for (int i = 1; i <=step; i++) {
isArrive(nums,i,nums[i],list);
}
}
}
跳跃游戏二
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
示例 1:
输入: nums = [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是2
。 从下标为 0 跳到下标为 1 的位置,跳1
步,然后跳3
步到达数组的最后一个位置。
示例 2:
输入: nums = [2,3,0,1,4] 输出: 2
方法一:
public class Jump {
// 记录最好移动次数
int count=Integer.MAX_VALUE;
/*
方法一:
递归回溯法 超出时间限制
* */
public int jump(int[] nums) {
//记录每次路径所需要的移动次数
int temp=0;
// 判断数组是否只有一个,是的话,直接返回
if (nums.length-1==0){
count=temp;
return count;
}
//获取最少移动次数的函数 通过不断递归和回溯
canJump(nums,0,nums[0],temp);
return count;
}
//寻找最优移动次数
public void canJump(int[] nums,int position,int step,int temp){
if(position+nums[position]>=nums.length-1){
++temp;
if(temp<count){
count=temp;
}
}
else {
for (int i = 1; i <=step; i++) {
canJump(nums,position+i,nums[position+i],++temp);
--temp;
}
}
}
方法二:
/*
方法二:
使用贪心算法
反向查找出发位置
我们的目标是到达数组的最后一个位置,因此我们可以考虑最后一步跳跃前所在的位置,该位置通过跳跃能够到达最后一个位置。
如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上来看,我们可以「贪心」
地选择距离最后一个位置最远的那个位置,
也就是对应下标最小的那个位置。因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。
找到最后一步跳跃前所在的位置之后,我们继续贪心地寻找倒数第二步跳跃前所在的位置,以此类推,直到找到数组的开始位置。
* */
public int jump2(int [] nums){
//指向最后一个元素
int position=nums.length-1;
//移动的次数
int step=0;
while (position>0){
for (int i = 0; i < position; i++) {
//找离position位置最远且可达到position的上一个位置 反向查找
if(i+nums[i]>=position){
position=i;
step++;
}
}
}
return step;
}
方法三:
/*
方法三
正向查找可到达的最大位置
如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
* */
public int jump3(int [] nums) {
//从初始位置开始
int position = 0;
//记录移动次数
int step = 0;
//记录position下一个可达的最远位置 ,说明因为最后position会>=nums.length 所以一次i永远没办法等于end,就会跳出循环,
//直接可以省略了 判断position+nums[position]>=nums.length
int end = 0;
//直接省略
/* if (position + nums[position] >= nums.length - 1) {
step++;
return step;
}*/
for (int i = 0; i < nums.length - 1; i++) {
position = Math.max(position, i + nums[i]);
if (i == end) {
end = position;
step++;
}
}
return step;
}
H指数
给你一个整数数组 citations
,其中 citations[i]
表示研究者的第 i
篇论文被引用的次数。计算并返回该研究者的 h
指数。
根据维基百科上 h 指数的定义:h
代表“高引用次数” ,一名科研人员的 h
指数 是指他(她)至少发表了 h
篇论文,并且每篇论文 至少 被引用 h
次。如果 h
有多种可能的值,h
指数 是其中最大的那个。
示例 1:
输入:citations = [3,0,6,1,5]
输出:3 解释:给定数组表示研究者总共有5
篇论文,每篇论文相应的被引用了3, 0, 6, 1, 5
次。 由于研究者有3
篇论文每篇 至少 被引用了3
次,其余两篇论文每篇被引用 不多于3
次,所以她的 h 指数是3
。
示例 2:
输入:citations = [1,3,1] 输出:1
方法一:
/*
方法一:
首先将将数据以递增的方式排序成有序
* */
public int hIndex(int[] citations) {
//临时变量
int temp=0;
//对数组进行排序
for (int i = 0; i < citations.length-1 ; i++) {
for (int j = 0; j <citations.length - i-1; j++) {
if(citations[j]>citations[j+1]){
temp= citations[j];
citations[j]=citations[j+1];
citations[j+1]=temp;
}
}
}
//获取数组中的最大值
int max=citations[citations.length-1];
//记录 数组中比max值大的个数
int count=0;
int h=0;
for (int i = max; i >=0; i--) {
//每次循环,count重新归零
count=0;
for (int j = 0; j < citations.length; j++) {
if(citations[j]>=i){
count++;
}
}
if(count>=i){
h=i;
return h;
}
}
return h;
}
方法二:
/*方法二:
二分查找法
H就是被引用数,一定是在0,citations.length之间
采用二分查找法,每次去0,到citations.length的中间mid ,然后遍历数组,看有多少值大于等于mid,改值记位count
比较count的值是否大于等于mid,如果是的话,H就是在区间的右边,然后在右边进行二分查找,不断缩小区间范围,最终找到H。
如果不是的话,H在左边,然后在左边进行二分查找,不断缩小区间范围,最终找到H。
* */
public int hIndex2(int[] citations){
//表示最左边界
int left=0;
//最右边界
int right=citations.length;
//中间值
int min=0;
while (left<right){
int count=0;
//加1防止死循环
min=(left+right+1)/2;
for (int i = 0; i < citations.length; i++) {
if(citations[i]>=min){
count++;
}
}
if(count>=min){
left=min;
min=(left+right)/2;
}
else {
right=min-1;
min=(right+left)/2;
}
}
return left;
}
O(1) 时间插入、删除和获取随机元素
哈希表查找元素 时间复杂度为O(1) 实现RandomizedSet 类: RandomizedSet() 初始化 RandomizedSet 对象 bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。 bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。 int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。 你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。 示例: 输入 ["RandomizedSet", "insert", "remove", "insert", "getRandom", "remove", "insert", "getRandom"] [[], [1], [2], [2], [], [1], [2], []] 输出 [null, true, false, true, 2, true, false, 2] 解释 RandomizedSet randomizedSet = new RandomizedSet(); randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。 randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。 randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。 randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。 randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。 randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。 randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 主要利用哈希表 +可变长数组(可以List集合)
public class RandomizedSet {
Random random ;
Set<Integer> set;
List<Integer> list;
//成员变量初始化
public RandomizedSet() {
set = new HashSet<>();
random=new Random();
list=new ArrayList<>();
}
//元素插入操作
public boolean insert(int val) {
boolean flag=set.add(val);
if(flag){
list.add(val);
}
return flag ;
}
//元素删除操作
public boolean remove(int val) {
boolean flag=set.remove(val);
if(flag){
list.remove(Integer.valueOf(val));
}
return flag;
}
// 随机获取一个元素
public int getRandom() {
return list.get( random.nextInt(list.size()));
}
public static void main(String[] args) {
RandomizedSet randomizedSet=new RandomizedSet();
randomizedSet.insert(1);
randomizedSet.remove(2);
randomizedSet.insert(2);
System.out.println(randomizedSet.getRandom());
randomizedSet.remove(1);
randomizedSet.insert(2);
System.out.println(randomizedSet.getRandom());
}
}
除自身以外数组的乘积
除自身以外数组的乘积 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度内完成此题。 示例 1: 输入: nums = [1,2,3,4] 输出: [24,12,8,6] 示例 2: 输入: nums = [-1,1,0,-3,3] 输出: [0,0,9,0,0] 方法一
/* 方法一 使用了除法,不符合题目要求*/
public int[] productExceptSelf(int[] nums) {
//最终要输出的结果
int [] answer=new int[nums.length];
//记录连乘最大值 分为count=1,和count=0的情况
int sum=1;
//记录数组中,有几个元素值为0
int count=0;
//记录数组元素中值为0的下标
int indexZero=0;
for (int i = 0; i < nums.length; i++) {
if(nums[i]==0){
count++;
indexZero=i;
}
}
//count值大于等于2的情况
if(count>=2){
return answer;
}
//count值等于1的情况
else if(count==1){
for (int i = 0; i < nums.length; i++) {
if(i!=indexZero){
sum=sum*nums[i];
}
answer[indexZero]=sum;
}
return answer;
}
//count值等于0等情况
else {
for (int i = 0; i < nums.length; i++) {
sum=sum*nums[i];
}
for (int i = 0; i < nums.length; i++) {
answer[i]=sum/nums[i];
}
return answer;
}
方法二:
/*方法二
左右乘积列表
将i左侧乘积的结果 放进 left[i] 中,将i右侧的结果放进 right[i]中
当left,right 数组填充完成后
对nums 进行遍历 answer=left[i]*right[i]
* */
public int[] productExceptSelf2(int[] nums) {
int [] answer=new int[nums.length];
//存放每个 i 的左乘积值
int []left=new int[nums.length];
//存放每个i的右乘积值
int []right=new int[nums.length];
left[0]=1;
for (int i = 1; i < nums.length; i++) {
left[i]=nums[i-1]*left[i-1];
}
right[nums.length-1]=1;
for (int i = nums.length-2; i >=0 ; i--) {
right[i]=right[i+1]*nums[i+1];
}
for (int i = 0; i < nums.length; i++) {
answer[i]=right[i]*left[i];
}
return answer;
}
方法三:
/*
左右乘积列表优化 不需要新建左,右数组,直接把answer,和nums 当作左右数组
* */
public int[] productExceptSelf3(int[] nums){
int [] answer=new int[nums.length];
//先对answer 数组全部值赋值为1,方便后面乘积 ,因为未初始化的数组元素值为0,会影响乘积结果
for (int i = 0; i < answer.length; i++) {
answer[i]=1;
}
//右乘积时,最后一个元素的右乘积的值为1
answer[nums.length-1]=1;
//左乘积时,第一个元素的左乘积的值为1
answer[0]=1;
//从左往右 进行乘积 获取每个 i的左乘积值
for (int i = 1; i < nums.length; i++) {
answer[i]=answer[i-1]*nums[i-1];
}
//从右 往左进行乘积 获取每个i 的右乘积值, 右因为answer已经是左乘积表了,所以往右乘积实际就是求 i的最终乘积
for (int i = nums.length-2; i >=0; i--) {
answer[i]=answer[i]*nums[i+1];
nums[i]=nums[i]*nums[i+1];
}
return answer;
}
加油站
在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。 示例 1: 输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2] 输出: 3 解释: 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 因此,3 可为起始索引。 示例 2: 输入: gas = [2,3,4], cost = [3,4,3] 输出: -1 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。
方法一:
/*一次遍历
总结:如果x到不了y+1(但能到y),那么从x到y的任一点出发都不可能到达y+1。因为从其中任一点出发的话,
相当于从0开始加油,而如果从x出发到该点则不一定是从0开始加油,可能还有剩余的油。既然不从0开始都到不了y+1,
那么从0开始就更不可能到达y+1了...
如果从x到不了y+1,那就从y+1,开始继续遍历。
* */
public int canCompleteCircuit(int[] gas, int[] cost) {
//数组的长度
int n = gas.length;
//从0开始遍历
int i = 0;
//如果i等于n 证明从哪一个加油站都绕不了一圈
while (i < n) {
int sumOfGas = 0, sumOfCost = 0;
int cnt = 0;
//能到达那个加油站,if cnt等于n 代表发现可达路径,返回i
while (cnt < n) {
int j = (i + cnt) % n;
sumOfGas += gas[j];
sumOfCost += cost[j];
if (sumOfCost > sumOfGas) {
break;
}
cnt++;
}
if (cnt == n) {
return i;
} else {
i = i + cnt + 1;
}
}
return -1;
}
分发糖果
n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求,给这些孩子分发糖果: 每个孩子至少分配到 1 个糖果。 相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。 示例 1: 输入:ratings = [1,0,2] 输出:5 解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。 示例 2: 输入:ratings = [1,2,2] 输出:4 解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。
/*
相邻两个孩子评分高的会获得更多的糖果。
意思就是说,每个元素必须与他们的左右之间的相邻的元素比较,
只要满足,x元素大于左边,或者大与右边相邻元素。即x元素必定比相邻左边元素或右边元素获得的糖果数量加一
int left[]数组 存放满足左规则元素获得的糖果数
将这个比较方式分为从左往右遍历,比较ratings[i]>ratings[i-1] 称为左规则
int right[] 数组存放满足右规则元素获得的糖果数
将这个比较方式分别从右往左遍历,比较ratings[i]>ratings[i+1] 称为右规则
每个人分到的糖果数一定是left[i]和right[i] 的最大值。
在实际代码中,我们先计算出左规则 left 数组,在计算右规则的时候只需要用单个变量记录当前位置的右规则,同时计算答案即可
* */
public int candy(int[] ratings) {
int [] left=new int[ratings.length];
for (int i = 0; i < ratings.length; i++) {
if(i>0&&ratings[i]>ratings[i-1]){
left[i]=left[i-1]+1;
}else {
left[i]=1;
}
}
int right=0;
int sum=0;
for (int i = ratings.length-1; i >=0; i--) {
if(i<ratings.length-1&&ratings[i]>ratings[i+1]){
right++;
}
else {
right=1;
}
sum=sum+Math.max(right,left[i]);
}
return sum;
}