力扣初级算法练习
数组
1.删除排序数组中的重复项
class Solution {
public int removeDuplicates(int[] nums) {
int index = 0;
int count = 1;
for(int i = 1;i<nums.length;i++){
if(nums[index]==nums[i]){
continue;
}
else{
nums[index+1]=nums[i];
index++;
count++;
}
}
return count;
}
}
2.购买股票的最佳时机
在刚看到这个题时,感觉就是找到两个数,一个最大一个最小,而且最小值要在最大值的左边,但是看了示例后发现不是我所想的那样,比我想象的要复杂一些,因为每天都能对股票进行买卖,看了一会感觉没有头绪,无从下手,看了一下解析,理解了一些,把每天的股票状态分成两种,一种是有股票,一种是没有股票,因为题上说了只能持有一个股票,那就只有这两种状态,然后分别·计算两种状态的最大利润。
如果当天没有股票,那可以分成两种情况,第一是当天没有进行股票交易,那最大利润就是前一天没有股票的最大利润,第二是当天进行了股票交易,那就只有一种可能,那就是前一天买了股票,因为如果是当天买了股票又卖出去,那当天的交易就没有意义了,最大利润仍然是前一天的没有股票的最大利润,所以最大利润应该是前一天有股票的最大利润加上当天卖掉股票所获得的利润。然后对这两种情况的金额进行对比,取最大的那一个作为当天手里没有股票的最大利润。
如果当天手里有股票,那也可以分成两种情况,第一是前一天买的股票,那当天的最大利润就是前一天有股票的最大利润,第二就是当天买了股票,但是没有卖出去,那当天的最大利润就是前一天没有股票的最大利润减去当天买股票所花费的金钱,就得到当天的最大利润。然后也对这两种情况进行对比,取最大的那个数值作为当天手里有股票的最大利润值。
那么大致思路就有了,最后一天一定是手里没有股票利润最大,返回最后一天手里没有股票的最大利润值就可以了。
具体代码实现如下
//动态规划解决
class Solution {
//0表示手里没有股票,1表示手里有股票
public int maxProfit(int[] prices) {
int length = prices.length;
//设置一个二维数组,当为1时,代表当天手里有股票,为0时手里没有股票。
int [] [] dp = new int [length] [2];
//设置初始值,第一天手里有股票的最大利润
dp[0][1] = -prices[0];
//第一题手里没有股票的最大利润
dp[0][0] = 0;
//循环求每一天的最大利润
for(int i = 1;i<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 dp[length-1][0];
}
}
上面的代码是采用动态规划的思想来进行设计,还有一种方法可以用贪心算法来进行计算。
贪心算法就是在每一步都采取最有利或最优的方式,以此来确保最终结果也是最优的,那对于这个买股票的问题,贪心算法的思想就是求利润最大,如何使得利润最大?那就是在最低价时买入,在最高价时卖出,那就有大致思路了,先在草稿纸上随便画一个折线图,每一个拐点都是一个相应的最高值或最低值,在最低时买入,在下一个最高值时卖出,最后统计一共有多少次这样的情况,把每次的利润都加起来,那最后的结果就是最大的利润。
代码如下
//贪心算法解决
class Solution {
public int maxProfit(int[] prices) {
//定义length为prices的最大值
int length = prices.length;
//设置指针初始位置
int index = 0;
//初始化最大利润为零
int total =0;
//当指针小于数组长度时,执行循环
while(index<length){
//为了确保指针不溢出,在这个循环中,index的值需要小于length-1,这个循环目的是寻找最低点
while(index<length-1&&prices[index]>=prices[index+1])
index++;
//如果第一个循环执行失败,那么记录最低点
int min = prices[index];
//这个循环是寻找最高点
while(index<length-1&&prices[index]<=prices[index+1])
index++;
//当第二个循环执行失败后,表明找到了最高点,最高点减去最低点就得到利润,在这个语句中,index++作用是在执行完这个语句后,进入下一次循环时,index的值进行一次自加。
total +=prices[index++]-min;
}
//返回最大利润
return total;
}
}
3.旋转数组
这道题不是很难,首先就能想到取模这个方法,但是有没有其他方法呢,参考了一下评论区大佬的代码,还有一种方法,首先第一种取模计算的方法。
class Solution {
public void rotate(int[] nums, int k) {
int length = nums.length;
int [] temp = new int [length];
int index = 0;
for(int i = 0;i<length;i++){
index = (i+k)%length;
temp[index] = nums[i];
}
for(int j = 0;j<length;j++){
nums[j] = temp[j];
}
}
}
整体反转的方法,这个方法的思想就是先把全部的数组反转一下,然后反转前半部分,最后反转后半部分,就能得到最后结果。这个方法要自己写一个反转数组的方法,能够把特定的索引之间的数据反转。
class Solution {
public void rotate(int[] nums, int k) {
int length = nums.length;
k = k%length;
reverse(nums,0,length-1);
reverse(nums,0,k-1);
reverse(nums,k,length-1);
}
public void reverse(int[] nums,int start,int end){
while(start<end){
int temp = nums[start];
nums[start++] = nums[end];
nums[end--] = temp;
}
}
}
4.存在重复元素
看到这道题之后,要求是找存在重复的元素,那么很容易就能想到set集合,set集合只能存在一个相同元素,在添加元素时,如果出现重复元素,那么会添加失败,那么这道题的解题思路大概就是,把数组中的元素全部添加到集合中,当添加失败时,那么这个数组中就存在重复元素,那么返回true,反之则返回false。
class Solution {
public boolean containsDuplicate(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int number: nums){
if(!set.add(number)){
return true;
}
}
return false;
}
}
5.只出现一次的数字
这道题要求是找只出现一次的数字,那首先就能想到的是就是set集合,set集合中只能添加一个元素,不能出现相同的元素,那就把数组里的数据都添加到set集合中,当添加失败时,那么就说明这个数据已经出现过了,此时删除这个数据,当把数组中的元素全部添加进数组之后,那么剩下的那个就是只出现一次的数字。
class Solution {
public int singleNumber(int[] nums) {
Set<Integer> set = new HashSet<>();
for(int number: nums){
if(!set.add(number)){
set.remove(number);
}
}
return (int)set.toArray()[0];
}
}
在看了评论区的方法后,发现还有一种方法,使用异或运算,异或运算的运算规则是: 在Java中,异或运算是以二进制的形式进行计算的,当进行异或运算时,会先把两个数值转换成二进制,在进行异或运算。在同位上的数值,相同则为0(都是0或者都是1),不同则为1(一个为0一个为1)。
运算定理: 1、结合律:(ab)c=a(bc)
2、交换律:ab=ba
3、与自身异或:a^a=0
4、与0异或:a^0=a
那么对于这一道题,我们把所有的数字都异或运算一次,根据异或运算的交换律,那么我们可以想到,把所有重复的元素进行一次异或元素,那么异或运算之后的结果都是0,那么最后剩下那个只出现过一次的元素,让这个元素与0进行异或运算,那么最终的结果仍然是这个数本身。
class Solution {
public int singleNumber(int[] nums) {
int result = 0;
for(int i = 0;i<nums.length;i++){
result^=nums[i];
}
return result;
}
}
6.两个数组的交集
看到这个题之后,题目是求两个数组的交集,首先想到的就是用循环来写,循环比较两个数组的元素,如果出现相同元素,就把元素记录到一个新的数组中,最后返回这个数组。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int len1 = nums1.length;
int len2 = nums2.length;
int min = (len1<len2)?len1:len2;
int [] ans = new int[min];
int i = 0;int j = 0;int k =0;
while(i<len1&&j<len2){
if(nums1[i]==nums2[j]){
ans[k++] = nums1[i];
i++;
j++;
}
else if(nums1[i]>nums2[j]){
j++;
}
else if(nums1[i]<nums2[j]){
i++;
}
}
return Arrays.copyOfRange(ans,0,k);
}
}
这段代码对于新建数组的处理有些麻烦,可以使用list集合来实现,先把数据存到list集合中,然后把集合转化为数组返回,大致代码如下。
List<Integer> list = new ArrayList<>();
int index = 0;
int[] res = new int[list.size()];
for (int k = 0; k < list.size(); k++) {
res[index++] = list.get(k);
}
7.加一
这道题大概意思就是把数组里的元素看成一个整体作为一个整数,然后对这个整数加一,然后以数组形式返回,刚开始觉得有些过于简单,把末尾加一不久行了吗,想了一下发现不能直接加一,当有9时,不能直接加一,要把9转化为0,且前一位加一,实现代码如下。
class Solution {
public int[] plusOne(int[] digits) {
int length = digits.length;
//循环遍历数组,比较每个元素的值是否为9
for(int i = length-1;i>=0;i--){
//如果不是9,直接加一返回
if(digits[i]!=9){
digits[i]++;
return digits;
}
//如果是9,那就先把这个位置的变为0,当再次进行循环时,上一位数字如果不为9,就直接会加一返回,若为9则继续变为零循环
if(digits[i]==9){
digits[i] = 0;
}
}
//如果全部数字都是9,那就要把数组长度加一,首位数字为1,其余数字为0
int [] num = new int [length+1];
//把原数组的元素复制到新数组里,从新数组的1索引复制到最后,此时原数组的值全部都为0
System.arraycopy(digits,0,num,1,digits.length-1);
//对第一位数字赋值,赋值为1
num[0] = 1;
//返回新数组
return num;
}
}
8.移动零
这道题是个移动元素的题,需要注意移动时不要覆盖原元素值,所以在这里我选择创建一个新数组,先循环遍历判断每个值是否为0,不为零则把数据依次插入到新的数组中。因为题中的要求是在原数组的基础上修改,那最后再把新数组复制到原数组中。
class Solution {
public void moveZeroes(int[] nums) {
int index = 0;
int length = nums.length;
int [] num = new int [length];
for(int i = 0;i<length;i++){
if(nums[i]!=0){
num[index++] = nums[i];
}else{
continue;
}
}
for(int j = index+1;j<length;j++){
num[j] = 0;
}
System.arraycopy(num,0,nums,0,length);
}
}
这样写似乎有些麻烦,我们可以直接在原数组中进行修改,设定两个指针,一个指针向后遍历数组元素,判断元素是否为0,如果不为零,向另一个指针所指的位置赋值,当第一个指针移动到最后时,剩下的都是0
class Solution {
public void moveZeroes(int[] nums) {
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0)
nums[index++] = nums[i];
}
while (index < nums.length) {
nums[index++] = 0;
}
}
}
9.两数之和
首先想到的就是暴力求解
class Solution {
public int[] twoSum(int[] nums, int target) {
int [] twoSum = new int [2];
int length = nums.length;
for(int i = 0;i<length;i++){
for(int j = i+1;j<length;j++){
if(nums[i]+nums[j]==target){
twoSum[0] = i;
twoSum[1] = j;
}
}
}
return twoSum;
}
}
还可以使用哈希表来求解
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> m = new HashMap<>();
int length = nums.length;
//循环遍历数组
for(int i = 0;i<length;i++){
//当数组中存在一个值等于target-nums[i]时,返回这两个数的索引
if(m.get(target-nums[i])!=null){
return new int [] {m.get(target-nums[i]),i};
}
//如果没有找到,就把值和索引存储到哈希表中
m.put(nums[i],i);
}
//如果遍历结束仍然没有找到,那么就没有符合条件的,返回两个0
return new int[] {0,0};
}
}
10.有效的数独
这个题是要检查所给的数独是否成立,在看到这个题后,想到能写三个方法,分别判断所在行,所在列,所在九宫格内的独立性
代码如下
class Solution {
public boolean isValidSudoku(char[][] board) {
for (int row = 0; row < 9; row++) {
if (!rowIsValid(board, row)) {
return false;
}
}
for (int col = 0; col < 9; col++) {
if (!colIsValid(board, col)) {
return false;
}
}
for (int blockRow = 0; blockRow < 3; blockRow++) {
for (int blockCol = 0; blockCol < 3; blockCol++) {
if (!blockIsValid(board, blockRow, blockCol)) {
return false;
}
}
}
return true;
}
private boolean rowIsValid(char[][] board, int row) {
Set<Character> seen = new HashSet<>();
for (int col = 0; col < 9; col++) {
char num = board[row][col];
if (num != '.') {
if (seen.contains(num)) {
return false;
}
seen.add(num);
}
}
return true;
}
private boolean colIsValid(char[][] board, int col) {
Set<Character> seen = new HashSet<>();
for (int row = 0; row < 9; row++) {
char num = board[row][col];
if (num != '.') {
if (seen.contains(num)) {
return false;
}
seen.add(num);
}
}
return true;
}
private boolean blockIsValid(char[][] board, int blockRow, int blockCol) {
Set<Character> seen = new HashSet<>();
for (int row = blockRow * 3; row < (blockRow + 1) * 3; row++) {
for (int col = blockCol * 3; col < (blockCol + 1) * 3; col++) {
char num = board[row][col];
if (num != '.') {
if (seen.contains(num)) {
return false;
}
seen.add(num);
}
}
}
return true;
}
}
在这三个方法中,使用set集合来判断是否出现重复元素,如果出现重复元素,那么直接返回false,反之则返回true;
11.旋转图像
这是一道旋转图像的题,要求逆时针旋转九十度,那我们可以找到一个规律,我们可以把这个二位数组先进行上下交换,然后再按对角线把这个二位数组再次交换,交换完成之后,我们就可以得到这个二维数组旋转九十度之后的结果了。
class Solution {
public void rotate(int[][] matrix) {
int length = matrix.length;
//先上下交换
for (int i = 0; i < length / 2; i++) {
int temp[] = matrix[i];
matrix[i] = matrix[length - i - 1];
matrix[length - i - 1] = temp;
}
//在按照对角线交换
for (int i = 0; i < length; ++i) {
for (int j = i + 1; j < length; ++j) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
}
}