牛客专题

这篇博客详细介绍了多种算法问题的解决方案,包括剪绳子问题的动态规划和贪心策略,二维数组查找的两种方法,滑动窗口最大值的暴力和单调队列实现,以及如何用两个栈模拟队列操作。此外,还探讨了数组中重复数字、出现次数超过一半的数字、只出现一次的两个数字的查找策略,以及数组元素的重新排序和数值的整数次方计算。涉及数据结构和算法的深入理解和应用。
摘要由CSDN通过智能技术生成

剪绳子

思路:1.尽可能剪成长度为3的长度,其次是为2的长度
   最优: 3 。把绳子尽可能切为多个长度为 33 的片段,留下的最后一段绳子的长度可能为 0,1,2 三种情况。
   次优: 2 。若最后一段绳子长度为 2 ;则保留,不再拆为 1+1 。
   最差: 1 。若最后一段绳子长度为 1 ;则应把一份 3 +1 替换为 2 + 2

   2.动态规划,dp[i]表示长度为 i 时的最大长度。
   3.贪心,尽可能剪成长度为3的长度

public class Solution {
    public int cutRope(int target) {
        if(target <= 3)    return target - 1;
        if(target % 3 == 0){
            return (int)Math.pow(3, target / 3);
        }else if(target % 3 == 1){
            return 4 * (int)Math.pow(3, target / 3 - 1);
        }else{
            return 2 * (int)Math.pow(3, target / 3);
        }
    }
}

根据转移方程反推初始条件 dp[5] = dp[2] * dp[3] = 6,所以dp[2] = 2, dp[3] = 3;

public class Solution {
    public int cutRope(int n) {
        if(n < 2) return 0;
//         长度为3时,返回2
        if(n <= 3)    return n - 1;
        int[] dp = new int[n + 1];
        dp[2] = 2;
        dp[3] = 3;
        for(int i = 4; i <= n; i++){
            for(int j = 2; j <= i / 2; j++){
//                 dp[i] = dp[i] >= dp[j] * dp[i - j] ? dp[i] : dp[j] * dp[i - j];
                dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
            }
        }
        return dp[n];
    }
}

while循环结束后,n只有1,2,3,4四种情况,直接返回 n * res

public class Solution {
    public int cutRope(int n) {
        if(n <= 3)    return n - 1;
        int res = 1;
        while(n > 4){
            res *= 3;
            n -= 3;
        }
        return n * res;
    }
}

二维数组查找

1.从左下角或者右上角开始查找

public class Solution {
    public boolean Find(int target, int [][] array) {
        if(array.length == 0 || array[0].length == 0)    return false;
        int i = array.length - 1, j = 0;
        while(i >= 0 && j < array[0].length){
            if(array[i][j] == target){
                return true;
            }else if(target > array[i][j]){
                j++;
            }else{
                i--;
            }
        }
        return false;
    }
}

2.二分法查找,将二维数组“摊平”成一维数组matrix[mid_idx / n][mid_idx % n]

public class Solution {
    public boolean Find(int target, int [][] matrix) {
    int m = matrix.length;
    if (m==0) return false;
    int n = matrix[0].length;
    // 确定二分法左右序号
    int left = 0, right = n * m - 1;
    int mid_idx, mid_element;
    while (left <= right){
        mid_idx = (left + right) / 2;
        mid_element = matrix[mid_idx / n][mid_idx % n];
        if (target == mid_element)  return true;
        else {
            if (target > mid_element)   left = mid_idx + 1;
            else right = mid_idx - 1;
        }
    }
    return false;
    }
}

滑动窗口最大值

  1. 暴力
  2. 单调队列:单调队列一般用于维护局部的单调性
    边界条件:num.length == 0 || num.length < size || size == 0
import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        if(num.length == 0 || num.length < size || size == 0)    return new ArrayList<>();
        int k = num.length - size + 1;
        ArrayList<Integer> res = new ArrayList<>();
        for(int i = 0; i < k; i++){
            int temp = num[i];
            for(int j = i; j < i + size; j++){
                temp = Math.max(temp, num[j]);
            }
            res.add(temp);
        }
        return res;
    }
}
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        if(num.length == 0 || size == 0 || num.length < size) return res;
        Deque<Integer> queue = new LinkedList<>();
        int k = num.length - size + 1;
        // 还未形成窗口
        for(int i = 0; i < size; i++){
        	// 如果单调队列的队尾小于要入队的元素nums[i]
            if(!queue.isEmpty() && queue.peekLast() < num[i]){
                queue.pollLast();
            }
            queue.offerLast(num[i]);
        }
        res.add(queue.peek());
        // 形成窗口
        for(int i = size; i < num.length; i++){
        	// 窗口滑动时,nums[i - k] 移出了窗口,如果移出了窗口的值nums[i - k]刚好是最大值,需将 deque 内的对应元素一起删除
            if(queue.peek() == num[i - size]){
                queue.pollFirst();
            }
            while(!queue.isEmpty() && queue.peekLast() < num[i]){
                queue.pollLast();
            }
            queue.offerLast(num[i]);
            res.add(queue.peek());
        }
        return res;
    }
}

两个栈实现队列

思路:一个栈负责入队,一个栈负责出队。
注意:当出队的栈为空时,要把入队的栈全部出栈到出队的栈。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
    	// 如果栈为空,将栈1的元素全部弹出后压如
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
            return stack2.pop();
        // 栈不为空,直接返回栈顶
        }else{
            return stack2.pop();
        }
    }
}

第一次出现的字符

思路:哈希+遍历

public class Solution {
    //Insert one char from stringstream
    StringBuilder sb = new StringBuilder();
    char[] count = new char[256];
    public void Insert(char ch)
    {
        sb.append(ch);
        count[ch]++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(int i = 0; i < sb.length(); i++){
            if(count[sb.charAt(i)] == 1){
                return sb.charAt(i);
            }
        }
        return '#';
    }
}

数组中重复的数字

思路:哈希

import java.util.*;
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param numbers int整型一维数组 
     * @return int整型
     */
    public int duplicate (int[] numbers) {
        // write code here
        int n = numbers.length;
        int[] count = new int[n];
        for(int i = 0; i < n; i++){
            if(numbers[i] < 0 || numbers[i] > n){
                return -1;
            }
            count[numbers[i]]++;
        }
        for(int i = 0; i < n; i++){
            if(count[i] > 1){
                return i;
            }
        }
        return -1;
    }
}

数组中出现次数超过一半的数字

思路:1.哈希 2.摩尔投票
摩尔投票法:记录票数votes,众数res,遍历数组,如果num == res,票数+1,如果num != res,票数-1,当 票数 votes 等于 0 ,则假设当前数字 num 是众数;,最后验证这个数出现的次数是否超过一半。

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        HashMap<Integer, Integer> map = new HashMap<>();
        int len = array.length;
        for(int num : array){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for(int key : map.keySet()){
            if(map.get(key) > len / 2){
                return key;
            }
        }
        return 0;
    }
}
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int res = 0, votes = 0;
        for(int num : array){
            if(votes == 0) res = num;
            votes += num == res ? 1 : -1;
        }
        int count = 0;
        for(int num : array){
            if(res == num){
                count++;
            }
        }
        // 判断出现次数是否超过一半
        return count > array.length / 2 ? res : 0 ; 
    }
}

数组中只出现一次的两个数字

思路:1.哈希 2.位运算
位运算bitmask &= -bitmask,表示的是把bitmask二进制中最右边的1保留,其他位置全部变为0

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] array) {
        // write code here
        int[] res = new int[2];
        Arrays.sort(array);
        HashMap<Integer, Integer> map = new HashMap<>();
        for(Integer num : array){
//             if(map.containsKey(num)){
//                 map.put(num, map.get(num) + 1);
//             }else{
//                 map.put(num, 1);
//             }
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        int index = 0;
        for(Integer key : map.keySet()){
            if(map.get(key) == 1){
                res[index] = key;
                index++;
            }
        }
        return res;
    }
}
import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param array int整型一维数组 
     * @return int整型一维数组
     */
    public int[] FindNumsAppearOnce (int[] array) {
        // write code here
        int[] res = new int[2];
        int tmp = 0;
        for(int num : array){
            tmp ^= num;
        }
        // 得到第一位不是0的数
        int mask = 1;
        while((mask & tmp) == 0){
            mask <<= 1;
        }
		// 也可以直接tmp &= -tmp;
		
        // 分组
        int a = 0, b = 0;
        for(int arr : array){
            if((mask & arr) == 0){
                a ^= arr;
            }else{
                b ^= arr;
            }
        }
        res[0] = a < b ? a : b;
        res[1] = a < b ? b : a;
        return res;
    }
}

调整数组顺序

要求:奇数在偶数前面,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:如果不考虑相对位置,可以直接使用双指针,但是现在需要考虑位置。

调整数组顺序

不考虑相对位置

class Solution {
    public int[] exchange(int[] nums) {
        int l = 0, r = nums.length - 1;
        while(l < r){
            if(nums[l] % 2 == 1){
                l++;
                continue;
            }
            if(nums[r] % 2 == 0){
                r--;
                continue;
            }
            int temp = nums[l];
            nums[l] = nums[r];
            nums[r] = temp;
        }
        return nums;
    }
}

   //slow指向奇数存放的位置
   //fast碰到奇数时互换位置,碰到偶数时+1
   //如果找到奇数则与slow指向的数进行交换
class Solution {
    public int[] exchange(int[] nums) {
        int slow = 0, fast = 0;
        while(fast < nums.length){
            if(nums[fast] % 2 == 1){
                int temp = nums[fast];
                nums[fast] = nums[slow];
                nums[slow] = temp;
                slow++;
            }
            fast++;
        }
        return nums;
    }
}

考虑顺序

import java.util.*;


public class Solution {
    public int[] reOrderArray (int[] array) {
        if (array == null || array.length == 0) return new int[0];
        int n = array.length; 
        for (int i=0; i<n; ++i) {
            for (int j=0; j<n; ++j) {
                // 左边是偶数, 右边是奇数的情况
                if ((array[j] & 1) == 0 && (array[j+1] & 1) == 1) {
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                }
            }
        }
        return array;
    }
}

import java.util.*;


public class Solution {
    public int[] reOrderArray (int[] array) {
        // write code here
        if(array == null || array.length == 0)    return new int[0];
        int[] res = new int[array.length];
        int index = 0;
        for(int i = 0; i < array.length; i++){
            if((array[i] & 1) == 1){
                res[index++] = array[i];
            }
        }
        for(int i = 0; i < array.length; i++){
            if((array[i] & 1) == 0){
                res[index++] = array[i];
            }
        }
        return res;
    }
}

数值的整数次方

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent < 0){
            exponent = -exponent;
            base = 1 / base;
        }
        return fun(base, exponent);
  }
    double fun(double x, int n){
        if(n == 0)    return 1;
        double res = fun(x, n / 2);
        if(n % 2 == 0){
            return res * res;
        }else{
            return res * res * x;
        }
    }
}

包含min函数的栈

思路:一个正常栈,一个最小栈,出栈的时候,如果弹出的是就是最小值,则两个栈要同时弹出。

import java.util.Stack;

public class Solution {

    Stack<Integer> stack1 = new Stack<>();
    Stack<Integer> stack2 = new Stack<>();
    public void push(int node) {
        stack1.push(node);
        if(stack2.isEmpty() || stack2.peek() >= node){
            stack2.push(node);
        }
    }
    
    public void pop() {
        if(stack1.peek() == stack2.peek()){
            stack1.pop();
            stack2.pop();
        }else{
            stack1.pop();
        }
    }
    
    public int top() {
        return stack1.peek();
    }
    
    public int min() {
        return stack2.peek();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值