hello,你好鸭,我是Ethan,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行!💪💪💪
目前博客主要更新Java系列、数据库、项目案例、计算机基础等知识点。感谢你的阅读和关注,在记录的同时希望我的博客能帮助到更多人。🤝🤝🤝
人生之败,非傲即惰,二者必居其一,勤则百弊皆除。你所坚持最终会回报你!加油呀!✔️💪🏃
刷题
1、合并两个有序数组
自己解题:
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
nums1.length=m+n;
nums2.length=n;
//扩容,将nums2追加到num1
for(int i=m;i<nums1.length;i++)
nums1[m]=nums2[0];
sort(nums1);
}
// 先做一个统一的排序函数
public void sort(int [] array){
int k=array.length;
int flag=0;
for(int i=0;i<k-1;i--){
for (int j=0;j<k-i-1;j++){
flag=1;
if(array[j]>array[j+1])
{
swap(array[j],array[j+1]);
}
}
if (!flag)
{
break;
}
}
}
// 封装一个调转位置的函数
public void swap(int [] array,int i,int j)
{
int temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
//先理清思路,做出条例后再动手编码
官方解答:
(1)方法一:直接合并后排序
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
for (int i = 0; i != n; ++i) {
nums1[m + i] = nums2[i];
}
Arrays.sort(nums1);
}
}
(2)方法二:双指针
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = 0, p2 = 0;
int[] sorted = new int[m + n];
//利用cur指明将排序得到的数
int cur;
while (p1 < m || p2 < n) { //二者有一个结束了就结束了
//先将m或n=0的情况排除,接下来再去比较二者的大小,赋值给cur
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++];
}
//这里再将得到的cur赋值给sorted数组
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
}
(3)* 方法三:逆向双指针
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1, p2 = n - 1;
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;
}
}
}
2、移除元素
自己解答:
官方解答:
(1)方法一:双指针(一前一后,两者同时动)
(2)方法二:优化双指针(一头一尾,头动尾静)
3、删除有序数组中的重复项
(1)自己解答:
(2)官方题解:
4、删除有序数组中的重复项 II
注意挖掘隐藏信息与通解:
官方题解
5、多数元素
自己解答:
官方解答:
方法一:哈希表
方法二:排序
方法三:随机化
方法五:Boyer-Moore 投票算法(同归于尽消杀法)
将众数记为1,非众数记为-1.最后的一定是众数。
6、轮转元素
自己解答:
官方解答:
方法一:使用额外的数组
这里%的用处是使得长度一定在i+k之间,超出就循环至开头!!!!!!
方法二:环状替换
2024年4月14日16:41:53
7.买卖股票的最好时机(动态规划I)
自己的解法:
提示:超出时间限制
官方解法:
动态规划——
//1.一次遍历就OK。 // 设定一个最小值。遍历所有的点,进行两种操作: // 1.如果小于最小值就将其赋给最小值。 // 2.如果大于最小值则求后面的价格与最小值的差值。这样遍历完全之时,既完全考虑了又得到了最大差值。 //注意:——这种题目一定要避免循环嵌套
8.买卖股票的最好时机(动态规划II)
方法一:状态转移方程
根据上一题的思路,需要设置一个二维矩阵表示状态。
第 1 步:定义状态
状态 dp[i][j] 定义如下:
dp[i][j] 表示到下标为 i 的这一天,持股状态为 j 时,我们手上拥有的最大现金数。
注意:限定持股状态为 j 是为了方便推导状态转移方程,这样的做法满足 无后效性。
其中:
第一维 i 表示下标为 i 的那一天( 具有前缀性质,即考虑了之前天数的交易 );
第二维 j 表示下标为 i 的那一天是持有股票,还是持有现金。这里 0 表示持有现金(cash),1 表示持有股票(stock)。
第 2 步:思考状态转移方程
状态从持有现金(cash)开始,到最后一天我们关心的状态依然是持有现金(cash);
每一天状态可以转移,也可以不动。状态转移用下图表示:
说明:
由于不限制交易次数,除了最后一天,每一天的状态可能不变化,也可能转移;
写代码的时候,可以不用对最后一天单独处理,输出最后一天,状态为 0 的时候的值即可。
第 3 步:确定初始值
起始的时候:
如果什么都不做,dp[0][0] = 0;
如果持有股票,当前拥有的现金数是当天股价的相反数,即 dp[0][1] = -prices[i];
第 4 步:确定输出值
终止的时候,上面也分析了,输出 dp[len - 1][0],因为一定有 dp[len - 1][0] > dp[len - 1][1]。
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
// 0:持有现金
// 1:持有股票
// 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; 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[len - 1][0];
}
}
方法一:贪心算法
贪心算法的直觉:由于不限制交易次数,只要今天股价比昨天高,就交易。
下面对这个算法进行几点说明:
该算法仅可以用于计算,但 计算的过程并不是真正交易的过程,但可以用贪心算法计算题目要求的最大利润。下面说明等价性:以 [1, 2, 3, 4] 为例,这 4 天的股价依次上升,按照贪心算法,得到的最大利润是:
res = (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
= prices[3] - prices[0]
仔细观察上面的式子,按照贪心算法,在下标为 1、2、3 的这三天,我们做的操作应该是买进昨天的,卖出今天的,虽然这种操作题目并不允许,但是它等价于:在下标为 0 的那一天买入,在下标为 3 的那一天卖出。
为什么叫「贪心算法」
回到贪心算法的定义:(下面是来自《算法导论(第三版)》第 16 章的叙述)
贪心算法 在每一步总是做出在当前看来最好的选择。
「贪心算法」 和 「动态规划」、「回溯搜索」 算法一样,完成一件事情,是 分步决策 的;
「贪心算法」 在每一步总是做出在当前看来最好的选择,我是这样理解 「最好」 这两个字的意思:
「最好」 的意思往往根据题目而来,可能是 「最小」,也可能是 「最大」;
贪心算法和动态规划相比,它既不看前面(也就是说它不需要从前面的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的;
这道题 「贪心」 的地方在于,对于 「今天的股价 - 昨天的股价」,得到的结果有 3 种可能:① 正数,② 000,③负数。
本题贪心算法的决策是: 只加正数 。
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < len; i++) {
int diff = prices[i] - prices[i - 1];
if (diff > 0) {
res += diff;
}
}
return res;
}
}
2024年4月28日19:52:57
9.跳跃游戏
给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。
自己的题解:双重嵌套循环
像这种双重嵌套循环式最差的,一定还有更优解
官方题解:贪心算法
public class Solution {
public boolean canJump(int[] nums) {
int n = nums.length;
int rightmost = 0;
for (int i = 0; i < n; ++i) {
if (i <= rightmost) {//最大距离的条件
rightmost = Math.max(rightmost, i + nums[i]);
if (rightmost >= n - 1) {
return true;
}
}
}
return false;
}
}
民间高手
class Solution {
public boolean canJump(int[] nums) {
int farthermost = 0;
for (int i = 0; i <= farthermost && i < nums.length; i++) {
farthermost = Math.max(farthermost, i + nums[i]);
}
return farthermost >= nums.length - 1;
}
}
主要把思路理清了就好说,注意一点就是能到达的最大距离是有条件的,并不是每一个数组元素都能到达
10 .跳跃游戏 II
给定一个长度为 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]。
自己根本不会做
官方题解:
解题思路
这道题是典型的贪心算法,通过局部最优解得到全局最优解。以下两种方法都是使用贪心算法实现,只是贪心的策略不同。
方法二:正向查找可到达的最大位置
方法一虽然直观,但是时间复杂度比较高,有没有办法降低时间复杂度呢?
如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
例如,对于数组 [2,3,1,2,4,2,3],初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。
从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。
在具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1。
在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
class Solution {
public int jump(int[] nums) {
int length = nums.length;
int end = 0;//记载边界位置
int maxPosition = 0;
int steps = 0;
for (int i = 0; i < length - 1; i++) {//遍历每一个元素
maxPosition = Math.max(maxPosition, i + nums[i]); //更新能够到达的最大距离
if (i == end) {
//如果到达了边界则将边界更新为最大距离,这时候也该进行下一步跳跃了。就这样遍历完每一步跳跃之后就可以求出最小跳跃次数了
end = maxPosition;
steps++;
}
}
return steps;
}
}
2024年4月29日20:14:04
11.H指数
方法一:排序
class Solution {
public int hIndex(int[] citations) {
//java官方提供的库Arrays
Arrays.sort(citations);
int h = 0, i = citations.length - 1;
while (i >= 0 && citations[i] > h) {
h++;
i--;
}
return h;
}
}
方法二:计数排序
public class Solution {
public int hIndex(int[] citations) {
int n = citations.length, tot = 0;
int[] counter = new int[n + 1];
for (int i = 0; i < n; i++) {
if (citations[i] >= n) {
counter[n]++;
} else {
counter[citations[i]]++;
}
}
for (int i = n; i >= 0; i--) {
//确保了最大值的H
tot += counter[i];
if (tot >= i) {
return i;
}
}
return 0;
}
}
方法三:二分搜索(看了还是不会)
class Solution {
public int hIndex(int[] citations) {
int left=0,right=citations.length;
int mid=0,cnt=0;
while(left<right){
// +1 防止死循环
mid=(left+right+1)>>1;
cnt=0;
for(int i=0;i<citations.length;i++){
if(citations[i]>=mid){
cnt++;
}
}
if(cnt>=mid){
// 要找的答案在 [mid,right] 区间内
left=mid;
}else{
// 要找的答案在 [0,mid) 区间内
right=mid-1;
}
}
return left;
}
}
刷题总结
1、先写思路条例,再编码。
2、审题不够严谨。
3、注意极端情况。比如全部相等,长度为0
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤ 分享👥 留言💬thanks!!!
📚愿大家都能学有所得,功不唐捐!