关于数组的算法题:买卖股票的最佳时机,存在重复的数,合并两个有序数组,最大子序和

1. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。

示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

java答案:

1. 一次遍历,找最低谷后的最高峰
class Solution {
   public int maxProfit(int prices[]) {
        if(prices.length<2)
            return 0;
        int minprice = prices[0];
        int maxprofit = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] < minprice)
                minprice = prices[i];
            else if (prices[i] - minprice > maxprofit)
                maxprofit = prices[i] - minprice;
        }
        return maxprofit;
    }
}
时间复杂度是o(n),空间复杂度是o(1)
2. 找每次低谷后的最高峰,
class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length<=1){
            return 0;
        }
        int i=0;
        int min =prices[0];
        int max = 0;
        int profit =0;
        while(i<prices.length-1){
            while(i<prices.length-1 && prices[i] >= prices[i+1]){
                i++;
            }
            min = prices[i];
            System.out.println(min);
            if(i==prices.length-1){
                return profit;
            }else{
                 max =prices[i];
                 for(int j=i;j<prices.length-1;j++){
                    if(max< prices[j+1]){
                        max = prices[j+1];
                    }
                  }
            }
            if(profit<(max-min))
                profit = max-min;
            System.out.println(profit);
            while(i<prices.length-1 && prices[i] <= prices[i+1]){
                i++;
            }
        }
        return profit;
    }
}
3. 暴力法(双重for循环)时间复杂度是o(n^2),空间复杂度是o(1)

JavaScript答案:

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if(prices.length<2)
       return 0;
    let min = prices[0];
    let profit =0;
    for(var i=1;i<prices.length;i++){
        if(prices[i]<min){
           min = prices[i];
        }else if((prices[i]-min)>profit){
           profit = prices[i]-min;
        }
    }
    return profit;
};
2. 买卖股票的最佳时机II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

java答案:

  1. 峰谷法: 我们试图跳过其中一个峰值来获取更多利润,那么我们最终将失去其中一笔交易中获得的利润,从而导致总利润的降低。
    每个谷值买入,每个峰值卖出,相加的总利润一定大于最低谷值买入,最高峰值卖出的价格利润。A+B>C在这里插入图片描述
class Solution {
   public int maxProfit(int[] prices) {
        if(prices.length<=1){
            return 0;
        }
        int i = 0;
        int valley = prices[0]; //谷
        int peak = prices[0]; //峰
        int maxprofit = 0;  //最大利润
        while (i < prices.length - 1) {
            while (i < prices.length - 1 && prices[i] >= prices[i + 1])
                i++;
            valley = prices[i];
            while (i < prices.length - 1 && prices[i] <= prices[i + 1])
                i++;
            peak = prices[i];
            maxprofit += peak - valley;
        }
        return maxprofit;
    }
}
时间复杂度是o(n),空间复杂度是o(1)
  1. 简单的一次遍历
    在这里插入图片描述
    如上图所示:A+B+C =D,不需要查找每次的峰值和谷值
class Solution {
   public int maxProfit(int[] prices) {
        if(prices.length<=1){
            return 0;
        }
        int maxprofit = 0;  //最大利润
        for(int i=1;i<prices.length;i++){
            if(prices[i]>prices[i-1]){
                maxprofit +=prices[i]-prices[i-1];
            }
        }
        return maxprofit;
    }
}
3. 暴力法(不建议使用,超过时间限制)
class Solution {
   public int maxProfit(int[] prices) {
        return calculate(prices, 0);
    }
    public int calculate(int prices[], int s) {
        if (s >= prices.length)
            return 0;
        int max = 0;
        for (int start = s; start < prices.length; start++) {
            int maxprofit = 0;
            for (int i = start + 1; i < prices.length; i++) {
                if (prices[start] < prices[i]) {
                    int profit = calculate(prices, i + 1) + prices[i] - prices[start];
                    if (profit > maxprofit)
                        maxprofit = profit;
                }
            }
            if (maxprofit > max)
                max = maxprofit;
        }
        return max;
    }
}
时间复杂度是o(n^n),空间复杂度是o(n)

JavaScript答案:

1. 峰谷法
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if(prices.length<=1){
       return 0;
    }
    var i=0;
    var max =0;
    var peak = prices[0];
    var gu = prices[0];
    while(i<prices.length-1){
       while(i<prices.length-1 && prices[i]>=prices[i+1]){
             i++;
       }
       gu =prices[i];
       while(i<prices.length-1 && prices[i]<=prices[i+1]){
             i++;
       }
       peak = prices[i];
       max = max+peak-gu;
    }
    return max;
};
2。 简单的一次遍历
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    if(prices.length<=1){
       return 0;
    }
    var max =0;
    for(var i=1;i<prices.length;i++){
        if(prices[i]>prices[i-1]){
           max += prices[i]-prices[i-1];
        }
    }
    return max;
};
3. 存在重复的数

给定一个整数数组,判断是否存在重复元素。
如果任何值在数组中出现至少两次,函数返回 true。如果数组中每个元素都不相同,则返回 false。

示例 1:
输入: [1,2,3,1]
输出: true

示例 2:
输入: [1,2,3,4]
输出: false

示例 3:
输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

java答案:

1. 先排序,然后查看相邻的是否相等.
class Solution {
    public boolean containsDuplicate(int[] nums) {
        Arrays.sort(nums);
        for(int i=0;i<nums.length-1;i++){
            if(nums[i] == nums[i+1]){
                return true;
            }
        }
        return false;
    }
}
2. 使用set集合
class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<Integer>(nums.length);
        for(int i : nums){
            set.add(i);
        }
        return set.size() ==nums.length ? false:true;
    }
}
3. 使用hashMap实现
 class Solution {
    public boolean containsDuplicate(int[] nums) {
       Map<Integer,Integer> map = new HashMap();
       for(int i =0;i<nums.length;i++){
           if(map.containsKey(nums[i])){
               return true;
           }else{
               map.put(nums[i],i);
           }
       }
        return false;
    }
}
4. 使用了一个新的数组,找到数组中的最大值和最小值,如果新的数组中有一个值先等于2了,就代表有重复的
class Solution {
    public boolean containsDuplicate(int[] nums) {
        if (nums.length == 0) {
            return false;
        }
        int min = nums[0];
        int max = nums[0];
        for (int i = 0; i < nums.length; i++) {
            max = Math.max(max,nums[i]);
            min = Math.min(min,nums[i]);
        }
        int d = max - min;
        int[] arr = new int[d + 1];
        for (int i = 0; i < nums.length; i++) {
            arr[nums[i] - min]++;
            if (arr[nums[i] - min] == 2) {
                return true;
            }
        }
        return false;
    }
}

注意:
map:

  1. map中键不可以重复,值可以重复
  2. 1、 V put(K key, V value) :以键=值的方式存入Map集合
    2、 V get(Object key) :根据键获取值
    3、 int size():返回Map中键值对的个数
    4、 boolean containsKey(Object key) :判断Map集合中是否包含键为key的键值对
    5、 boolean containsValue(Object value) :判断Map集合中是否包含值为value键值对
    6、 boolean isEmpty():判断Map集合中是否没有任何键值对
    set集合:要求存入的元素没有重复,没有索引。
    1. 添加: set.add(元素);

JavaScript答案:

1.使用排序,然后比较
/**
 * @param {number[]} nums
 * @return {boolean}
 */
var containsDuplicate = function(nums) {
    if(nums.length<=1)
       return false;
    nums.sort();
    for(var i=0;i<nums.length-1;i++){
        if(nums[i]==nums[i+1]){
           return true;
        }
    }
    return false;
};
2.使用新的数组
var containsDuplicate = function(nums) {
    if(nums.length<=1)
       return false;
     var arr = [];
    for(var i = 0 ; i < nums.length;i++){       
        if(arr[nums[i]] == undefined){
            arr[nums[i]] = nums[i];
        }else{
            return true;
        }
    }
    return false;
};
3.使用Array.from()方法
var containsDuplicate = function(nums) {
    return Array.from(new Set(nums)).length !== nums.length;
};
4. 使用Array的every()方法
var containsDuplicate = function(nums) { 
    return !nums.every(x=>nums.indexOf(x)==nums.lastIndexOf(x));
};
5. 使用map,
var containsDuplicate = function(nums) { 
    let map = new Map();
    return nums.some(item => map.has(item) ? true : !map.set(item));
};

注意:

  1. 创建数组:var arr=[]; 或者var arr = new Array(可以写size);
  2. Array.from()方法从一个类似数组或可迭代对象中创建一个新的数组实例。
    参数:第一个:接受一个类似数组或可迭代对象。第二个:新数组每个元素都会执行的回调函数。第三个: 指定第二个参数的this对象
    const arr = [1, 2, 3];
    Array.from(arr); //[1, 2, 3]
    Array.from(‘foo’); // [‘f’, ‘o’, ‘o’]
  3. every() 方法用于检测数组所有元素是否都符合指定条件。
    every() 方法使用指定函数检测数组中的所有元素:
    如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
    如果所有元素都满足条件,则返回 true。
    注意: every() 不会对空数组进行检测。 every() 不会改变原始数组。
  4. some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
    some() 方法会依次执行数组的每个元素:
    如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
    如果没有满足条件的元素,则返回false。
    注意: some() 不会对空数组进行检测。 some() 不会改变原始数组。
  5. map.set(1),如果添加成功,返回true,否则返回false。
4. 合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明: 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]

java答案:

1. 使用了另外一个数组保存合并之后的,合并以后再赋给nums1,双指针,从前往后
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        if(n==0){
            return;
        }
        int i=0;
        int j=0;
        int x=-1;
        int[] arr = new int[m+n];
        while(i<m && j<n){
            if(nums1[i]<=nums2[j]){
                arr[++x] = nums1[i];
                i++;
            }else{
                arr[++x] = nums2[j];
                j++;
            }
        }
        while(i<m){
            arr[++x] = nums1[i];
            i++;
        }
        while(j<n){
            arr[++x] = nums2[j];
            j++;
        }
        for(int y=0;y<arr.length;y++){
            nums1[y] = arr[y];
        }
    }
}
时间复杂度是o(m+n),空间复杂度是o(m+n)
2. 双指针,从前往后,使用一个新的数组装nums1的数组,然后合并的直接放nums1数组中
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //使用一个新的数组放nums1
        int [] nums1_copy = new int[m];
        System.arraycopy(nums1, 0, nums1_copy, 0, m);
        //双指针,一个指向nums1_copy,一个指向nums2
        int p1 = 0;
        int p2 = 0;
        int p = 0; //p指向合并后的nums1的值
        while ((p1 < m) && (p2 < n))
          nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++];
        if (p1 < m)
          System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
        if (p2 < n)
          System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
    }
}
时间复杂度是o(m+n),空间复杂度是o(m)
3. 双指针,从后往前,这个不需要额外的数组
class Solution {
   public void merge(int[] nums1, int m, int[] nums2, int n) {
        int p1 = m - 1; //p1指向nums1的值
        int p2 = n - 1; //p2指向nums2的值
        int p = m + n - 1; //合并之后nums1的值
        while ((p1 >= 0) && (p2 >= 0))
          nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];
        System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
  }
}
时间复杂度是o(m+n),空间复杂度是o(1)
4. 先合并之后,在排序
class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
       System.arraycopy(nums2, 0, nums1, m, n);
       Arrays.sort(nums1);
    }
}
时间复杂度是o((m+n)log(m+n)),空间复杂度是o(1)

注意:

  1. System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 方法复制指定的源数组的数组,在指定的位置开始,到目标数组的指定位置。
    Object src : 原数组
    int srcPos : 从元数据的起始位置开始
      Object dest : 目标数组
      int destPos : 目标数组的开始起始位置
      int length : 要copy的数组的长度
  2. 拷贝一个数组,可能会使用System.arraycopy()或者Arrays.copyof()两种方式,区别:
    Arrays.copyOf()不仅仅只是拷贝数组中的元素,在拷贝元素时,会创建一个新的数组对象。而System.arrayCopy只拷贝已经存在数组元素。
    Array.copyOf() 用于复制指定的数组内容以达到扩容的目的,有两个参数,一个是数组,一个int类型的数。
    copied = Arrays.copyOf(arr, 3); 将创建一个新的数组,数据为复制长度为3的arr数组
    JavaScript答案:
1. 双指针,从后往前
/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    if(n==0){
      return ;   
    }
    var p1= m-1;
    var p2 = n-1;
    var p = m+n-1;
    while(p1>=0 && p2>=0){
          nums1[p--] = nums1[p1]>nums2[p2]?nums1[p1--]:nums2[p2--];
    }
    while(p2>=0){
          nums1[p--] =nums2[p2--];
    }
};
2. 双指针,从前往后
var merge = function(nums1, m, nums2, n) {
    if(n==0){
      return ;   
    }
    var p1=0;
    var p2=0;
    var arr=nums1.slice(0,m);
    var p=0;
    while(p1<m && p2<n){
        if(arr[p1]<=nums2[p2]){
           nums1[p++] = arr[p1++];
        }else{
           nums1[p++] = nums2[p2++]; 
        }      
    }
    while(p1<m){
       nums1[p++] = arr[p1++];
       //console.log("nums1[p]"+nums1[p]);
    }
    while(p2<n){
       console.log(p2);
       nums1[p++] = nums2[p2++];      
    }
};
3. 使用是splice的修改功能,
var merge = function(nums1, m, nums2, n) {
   nums1.splice(m, n, ...nums2);
   nums1.sort((a, b) => (a - b));
};
4. //注意不要用slice, 要用能直接修改原数组的方法,而且原数组不能重新赋值
var merge = function(nums1, m, nums2, n) {
    var i=0, j=0;
    nums1.splice(m);
    while(j<n) {
        if(i === nums1.length) {
            nums1.push(nums2[j++]);
            i++;
        } else {
            if (nums1[i]>nums2[j]) {
                nums1.splice(i,0,nums2[j]);
                i++;
                j++;
            } else {
                i++;
            }
        }
    }
};

注意:

  1. var arr=nums1.slice(0,m);//这个地方要是用slice方法,是生成一个新的数组赋给arr,如果使用var arr= nums1;这样如果nums1发生变化,arr也会跟着发生变化。
  2. arrayObject.splice(index,howmany,item1,…,itemX):index:必需,整数,要操作的位置。howmany:必需,删除的项目设置,如果是0,不会删除项目,后面的参数是可选的,向数组中添加的新项目。
  3. ECMAScript 6引入三个点“…”语法用来分别代表一个数组参数列表。
  4. js中创建数组,空数组中的数据开始都是undefined,不能直接加数,会返回NaN的。
5. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

java答案

1. 动态规划:主要是利用逐步求解,以连续数组结束位置为每一步的解,sum其实就是记录了上一步骤的解,在这一步骤进行对比,如果上一步骤的解<0则舍弃。最终得到这一步骤解,与之前步骤解的最大值res进行比较,保存当前的最优解。
class Solution {
    public int maxSubArray(int[] nums) {
        int sum=0;
        int res=nums[0];
        for(int num:nums){
            sum=sum>0?sum+num:num;
            if(res<sum){
                res=sum;
            }
        }
        return res;
    }
}
一次遍历,分情况讨论
class Solution {
    public int maxSubArray(int[] nums) {
        if(nums.length==1){
            return nums[0];
        }
        int count=nums[0];
        int max=nums[0];
        for(int i=1;i<nums.length;i++){
            if(nums[i]>0 && count<=0){
                count=nums[i];
            }else if(nums[i]<=0 &&count<=0){
                count=Math.max(count,nums[i]);
            }else{
                count+=nums[i];
            }
            if(count>max){
                max=count;
            }
        }
        return max;
    }
}
2. //分治法: 通过递归分治不断的缩小规模,问题结果就有三种,左边的解,右边的解,以及中间的解(有位置要求,从中间mid向两边延伸寻求最优解),得到三个解通过比较大小,等到最优解。
class Solution {
    public int maxSubArray(int[] nums) {
        return maxSubArrayPart(nums,0,nums.length-1);
    }
    private int maxSubArrayPart(int[] nums,int left,int right){
        if(left==right){
            return nums[left];
        }
        int mid=(left+right)/2;
        return Math.max(
            maxSubArrayPart(nums,left,mid),
            Math.max(
                maxSubArrayPart(nums,mid+1,right),
                maxSubArrayAll(nums,left,mid,right)
            )
        );
    }
    //左右两边合起来求解
    private int maxSubArrayAll(int[] nums,int left,int mid,int right){
        int leftSum=Integer.MIN_VALUE;
        int sum=0;
        for(int i=mid;i>=left;i--){
            sum+=nums[i];
            if(sum>leftSum){
                leftSum=sum;
            }
        }
        sum=0;
        int rightSum=Integer.MIN_VALUE;
        for(int i=mid+1;i<=right;i++){
            sum+=nums[i];
            if(sum>rightSum){
                rightSum=sum;
            }
        }
        return leftSum+rightSum;
    }
}

JavaScript答案:

1. 动态规划
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxSubArray = function(nums) {
    var result=nums[0];
    var sum=0;
    for(var i=0;i<nums.length;i++){
        if(sum>0){
          sum+=nums[i];   
        }else{
            sum=nums[i];
        }
        if(sum>result){
           result = sum;
        }
    }
    return result;
};
2. 分治法:
/**
 * @param {number[]} nums
 * @return {number}
 */
var maxAll = function(nums,left,mid,right){
    let left_max = nums[mid];
    var sum=0;
    for(var i=mid;i>=left;i--){
        sum+=nums[i];
        if(sum>left_max){
           left_max= sum;
        }
        console.log(left_max);
    }
    let right_max = nums[mid+1];
    sum=0;
    for(var i=mid+1;i<=right;i++){
        sum+=nums[i];
        if(sum>right_max){
           right_max= sum;
        }
    }
    return left_max+right_max;
};
var maxSub = function(nums,left,right){
    if(left==right){
       return nums[left];
    }
    let mid = Math.floor((left+right)/2);
    return Math.max(maxSub(nums,left,mid),
                   Math.max(maxSub(nums,mid+1,right),maxAll(nums,left,mid,right)));
};
var maxSubArray = function(nums) {
    return maxSub(nums,0,nums.length-1);
};

注意:

  1. Js中两个整数相除,不会自动转换成整数:Math.floor((left+right)/2);
  2. 浮点数的最大值和最小值是:Number.MAX_VALUE , Number.MIN_VALUE
  3. js中求幂的方法:Math.pow(数,指数);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值