【C#】LeetCode数组 / 字符串

数组/字符串

88.合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

1. 直接逃课

public class Solution {
    public void Merge(int[] nums1, int m, int[] nums2, int n) {
        Array.Copy(nums2, 0, nums1, m, n);
        Array.Sort(nums1);
    }
}

***2. 逆向双指针

从数组末尾最大值开始比较,取大值存在nums1中

public class Solution {
    public void Merge(int[] nums1, int m, int[] nums2, int n) {
        int point1 = m - 1;
        int point2 = n - 1;
        int tail = m + n - 1;
        while(point1 >= 0 || point2 >= 0)
        {
            if(point1 < 0)
            {
                nums1[tail] = nums2[point2];
                point2--;
            }else if (point2 < 0)
            {
                nums1[tail] = nums1[point1];
                point1--;
            }else if(nums1[point1] >= nums2[point2])
            {
                nums1[tail] = nums1[point1];
                point1--;
            }else
            {
                nums1[tail] = nums2[point2];
                point2--;
            }
            tail--;
        }
        
    }
}



27. 移除元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k。

1. 双指针优化

一个指向首,一个指向尾。首部等于val,则赋值尾部的值,同时尾部左移;否则首部右移。

public class Solution {
    public int RemoveElement(int[] nums, int val) {
        int tail = nums.Length - 1;
        int top = 0;
        while(top <= tail)
        {
            if(nums[top] == val)
            {
                nums[top] = nums[tail--];
            }else
            {
                top++;
            }
        }
        return tail + 1;//记得+1,因为返回的是个数
    }
}



26. 删除有序数组中的重复项

给你一个非严格递增排列的数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

1. 双指针

快慢指针,慢指针不等于快指针时,把快指针的值赋给慢指针。

public class Solution {
    public int RemoveDuplicates(int[] nums) {
        int len = nums.Length;
        int left = 0;
        int right = 1;
        while(right < len)
        {
            if(nums[left] == nums[right])
            {
                right++;
            }else
            {
                nums[++left] = nums[right++];
            }
        }
        return left + 1;//记得+1,因为返回的是个数
    }
}

80. 删除有序数组中的重复项 II

给你一个有序数组 nums ,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

1. 双指针

依旧是快慢指针,慢的是排好的排好的排好的!!快的是未检测的,想成两块区域,不要混乱。

慢指针不等于快指针时,把快指针的值赋给慢指针+2的位置。有点抽象,这里我有点理解得慢。

  • 其实就是不相等的时候不断把快的部分copy到慢的部分的+2。
  • +2和快的部分相等,慢的和快的不相等,依旧快的部分copy到慢的+2,因为慢的部分只会保留两个相等元素,所以再两次循环就会慢的等于快的。
  • 慢的等于快的时,快指针疯狂向后移,相当于忽略掉
public class Solution {
    public int RemoveDuplicates(int[] nums) {
        int left = 2;
        int right = 2;
        int len = nums.Length;
        if(len<=2){
            return len;
        }
        while(right < len)
        {
            if(nums[left-2] != nums[right])
            {
                nums[left] = nums[right];
                left++;
            }
            right++;
        }
        return left;
    }
}

169. 多数元素

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

1. 排序

排序后砍半返回,因为大于1/2次数的数排序后一定在中点。

public class Solution {
    public int MajorityElement(int[] nums) {
        int len = nums.Length;
        Array.Sort(nums);
        return nums[len/2];
    }
}

时间复杂度:O(nlogn)。

空间复杂度:O(logn)。

***2. Boyer-Moore 投票算法

public class Solution {
    public int MajorityElement(int[] nums) {
        int candidate = nums[0];
        int count = 0;
        foreach(int num in nums)
        {
            if(count == 0)
            {
                candidate = num;
            }
            count += (num == candidate) ? 1 : -1;
        }
        return candidate;
    }
}

时间复杂度:O(n)。

空间复杂度:O(1)。

189. 轮转数组

给定一个整数数组 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]

1. 新建一个数组

暴力解

public class Solution {
    public void Rotate(int[] nums, int k) {
        //方法一:额外使用一个数组
        int len = nums.Length;
        int[] tempArray = new int[len];
        k %= len;
        for(int i = 0; i < len; i++){
            int temp = i+k;
            if(temp >= len)
            {
                temp -= len;
            }
            tempArray[temp] = nums[i];
        }
         Array.Copy(tempArray, nums, len);
    }
}

时间复杂度: O(n),其中 n 为数组的长度。

空间复杂度: O(n)。

2. 环状替换

  • 计算数组长度和k值最大公约数,用来计算需要转几圈
  • 开始转圈,每一圈内分别把当前值替换到+k的位置
public class Solution {
    public void Rotate(int[] nums, int k) {
        //方法二:环状替换
        int len = nums.Length;
        k %= len;
        int circle = GCD(len, k);
        for(int i = 0; i < circle; i++)
        {
            int cur = i;
            int prev = nums[i];
            do
            {
                int next = (cur + k) % len;
                int temp = nums[next];
                nums[next] = prev;
                prev = temp;
                cur = next;
            }while(cur != i);
        }
        
    }
    //GCD 算法
    //欧几里得算法
    //计算 x 和 y 的最大公约数。这个值用于确定需要多少个循环才能完全完成旋转操作。
    //Always x > y
    private int GCD(int x, int y)
    {
        return y == 0 ? x : GCD(y, x % y);
    }
}

***3. 反转数组

反转3次数组。

例如nums[7]{1,2,3,4,5,6,7}, k = 3:

1 2 3 4 5 6 7 -> 7 6 5 | 4 3 2 1 -> 5 6 7 | 1 2 3 4

public class Solution {
    public void Rotate(int[] nums, int k) {
        //方法三:反转数组
        int len = nums.Length;
        Array.Reverse(nums);
        Array.Reverse(nums, 0, k%len);
        Array.Reverse(nums, k%len, len-k%len);
    }
}

其中: public static void Reverse (Array array, int index, int length);

也可以自己写Reverse()函数。

private static void Reverse(int[] nums, int start, int end)
    {
        while (start < end)
        {
            // 交换元素
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            
            // 移动指针
            start++;
            end--;
        }
    }

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

1. 一次遍历

用当前值减前序最小值,只需要维护前序最小值和当前最大利润就可以了。

public class Solution {
    public int MaxProfit(int[] prices) {
        int res = 0;
        int lowest = 100000;
        for(int i = 0; i < prices.Length; i++)
        {
            //用两个Math函数,就不需要if判断大小了
            res = Math.Max(res, prices[i] - lowest);
            lowest = Math.Min(prices[i], lowest);
        }
        return res;
    }
}

122. 买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

***1. 贪心算法

如果赚钱那么记录,跌了就不管。

贪心算法只能用于计算最大利润,计算的过程并不是实际的交易过程。

public class Solution {
    public int MaxProfit(int[] prices) {
        int res = 0;
        for(int i = 1; i < prices.Length; i++)
        {
            //也可以直接一个 res += Math.Max(0, prices[i] - prices[i-1])
            int temp = prices[i] - prices[i-1];
            if(temp > 0)
            {
                res += temp;
            }
        }
        return res;
    }
}

2. 动态规划

  • dp[i][0]为当天手中不持有股票

    计算方法:Max(前一天不持有股票&当天不买,前一天持有股票&当天卖出)

  • dp[i][1]为当天手中持有股票

    计算方法:Max(前一天持有股票&当天不卖,前一天不持有股票&当天买入)

返回值为dp[len-1, 0],因为最后一天必定卖出。

public class Solution {
    public int MaxProfit(int[] prices) {
        int len = prices.Length;
        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];
    }
}

因为最后只返回dp[len-1, 0]可以做一些简化:

public class Solution {
    public int MaxProfit(int[] prices) {
        int len = prices.Length;
        int res_sold = 0;
        int res_hold = -prices[0];
        for(int i = 1; i < len; i++)
        {
            res_sold = Math.Max(res_sold, res_hold + prices[i]);
            res_hold = Math.Max(res_hold, res_sold - prices[i]);
        }
        return res_sold;
    }
}

55. 跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。

1. 我的第一个想法

维护一个布尔数组,后置节点可到达则为true,依次遍历。

**但要嵌套两层循环,比较慢。并且还需要另一个数组来存储,也耗空间。

public class Solution {
    public bool CanJump(int[] nums) {
        int len = nums.Length;
        bool[] jump = new bool[len];
        jump[0] = true;
        for(int i = 0; i < len; i++)
        {
            int temp = nums[i];
            //如果当前节点可达,才进行后续处理
            if(jump[i])
            {
                //已经超过最大长度则返回true
                if(temp + i >= len - 1)
                {
                    return true;
                }
                //未达最大长度时,后续可达节点均设置为true
                for(int j = 1; j <= temp; j++)
                {
                    jump[i + j] = true;
                }
            }
        }
        return false;
    }
}

***2. 贪心算法

依次遍历数组中的每一个位置,并实时维护最远可以到达的位置。

public class Solution {
    public bool CanJump(int[] nums) {
        int len = nums.Length;
        int maxJump = 0;
        for(int i = 0; i < len; i++)
        {
            //根据前置最大步长,无法到达当前节点时,直接返回false结束循环
            if(i > maxJump)
            {
                return false;
            }
            //计算最大步长
            maxJump= Math.Max(maxJump, i + nums[i]);
            //已经超过最大长度则返回true
            if(maxJump >= len - 1)
            {
                return true;
            }
        }
        return true;
    }
}

45. 跳跃游戏 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]。

1. 动态规划

维护到达当前位置的最小步数
dp公式:min(到达此位置的当前最小步数,上一个位置最小步数加1跳过来)

public class Solution {
    public int Jump(int[] nums) {
        int len = nums.Length;
        int[] dp = new int[len];
        for(int i = 0; i < len; i++)
        {
            dp[i] = 10000;
        }
        dp[0] = 0;
        for(int i = 0; i < len; i++)
        {
            for(int j = 1; j <= nums[i]; j++)
            {
                if(i + j < len)
                {
                    dp[i + j] = Math.Min(dp[i] + 1, dp[i + j]);
                }
                
            }
        }
        return dp[len-1];
    }
}

***2. 贪心算法

在循环中干两件事:

  1. 对比记录位于当前节点时,后续可以到达的最远节点
  2. 当前节点为上次跳跃后节点记录的可达最远节点时,执行跳跃,并且记录本次跳跃结束下次执行跳跃的节点

也就是说,在循环中两件事情其实是独立处理的,一边贪心找最远节点,一边执行跳跃操作。

  • i<len-1的原因:

    因为在访问最后一个元素之前,记录的最远节点一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。

public class Solution {
    public int Jump(int[] nums) {
        int len = nums.Length;
        //跟踪当前节点可到达的最远节点
        int maxJump = 0;
        //上次跳跃结束达到的节点位置,后续可跳到的最远节点
        int prevMax = 0;
        int res = 0;
        for(int i = 0; i < len-1; i++)
        {
            //记录当前位置时,后续可跳到的最远节点
            maxJump = Math.Max(maxJump, i + nums[i]);
            //执行跳跃
            if(i == prevMax)
            {
                res++;
                prevMax = maxJump;
            }
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值