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答案:
- 峰谷法: 我们试图跳过其中一个峰值来获取更多利润,那么我们最终将失去其中一笔交易中获得的利润,从而导致总利润的降低。
每个谷值买入,每个峰值卖出,相加的总利润一定大于最低谷值买入,最高峰值卖出的价格利润。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)
- 简单的一次遍历
如上图所示: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:
- map中键不可以重复,值可以重复
- 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集合:要求存入的元素没有重复,没有索引。- 添加: 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));
};
注意:
- 创建数组:var arr=[]; 或者var arr = new Array(可以写size);
- Array.from()方法从一个类似数组或可迭代对象中创建一个新的数组实例。
参数:第一个:接受一个类似数组或可迭代对象。第二个:新数组每个元素都会执行的回调函数。第三个: 指定第二个参数的this对象
const arr = [1, 2, 3];
Array.from(arr); //[1, 2, 3]
Array.from(‘foo’); // [‘f’, ‘o’, ‘o’] - every() 方法用于检测数组所有元素是否都符合指定条件。
every() 方法使用指定函数检测数组中的所有元素:
如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
如果所有元素都满足条件,则返回 true。
注意: every() 不会对空数组进行检测。 every() 不会改变原始数组。 - some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。 some() 不会改变原始数组。 - 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)
注意:
- System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 方法复制指定的源数组的数组,在指定的位置开始,到目标数组的指定位置。
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length : 要copy的数组的长度 - 拷贝一个数组,可能会使用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++;
}
}
}
};
注意:
- var arr=nums1.slice(0,m);//这个地方要是用slice方法,是生成一个新的数组赋给arr,如果使用var arr= nums1;这样如果nums1发生变化,arr也会跟着发生变化。
- arrayObject.splice(index,howmany,item1,…,itemX):index:必需,整数,要操作的位置。howmany:必需,删除的项目设置,如果是0,不会删除项目,后面的参数是可选的,向数组中添加的新项目。
- ECMAScript 6引入三个点“…”语法用来分别代表一个数组参数列表。
- 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);
};
注意:
- Js中两个整数相除,不会自动转换成整数:Math.floor((left+right)/2);
- 浮点数的最大值和最小值是:Number.MAX_VALUE , Number.MIN_VALUE
- js中求幂的方法:Math.pow(数,指数);