算法--java上

发布时间 2023-04-13 11:04:35作者: 福尔摩才

算法入门--java

二分查找

1.在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109
class Solution {

    public int leftMargin(int[] nums,int target){//查找左边界
        int low = 0;
        int high = nums.length - 1;
        while(low <= high){
            int mid = low + (high-low)/2;
            if(nums[mid] < target){
                low = mid + 1;
            }else if(nums[mid] > target){
                high = mid - 1;
            }else if(nums[mid] == target){
                high = mid - 1;
            }
        }
        if (nums[low] == target) {
            return low;
        } else {
            return -1;
        }
    }
    public int rightMargin(int[] nums,int target){//查找右边界
        int low = 0;
        int high = nums.length - 1;
        int rm = 0;
        while(low <= high){
            int mid = low + (high-low)/2;
            if(nums[mid] < target){
                low = mid + 1;
            }else if(nums[mid] > target){
                high = mid - 1;
            }else if(nums[mid] == target){
                low = mid + 1;
                rm = low;
            }
        }
        if (nums[high] == target) {
            return high;
        } else {
            return -1;
        }
    }
    
    public int[] searchRange(int[] nums, int target) {
        if (nums.length == 0 || nums[0] > target || nums[nums.length - 1] < target) {
            return new int[] {-1,-1};
        }
        int lm = leftMargin(nums,target);
        int rm = rightMargin(nums,target);
        return new int[] {lm,rm};
    }
}

2.搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1

示例 3:

输入:nums = [1], target = 0
输出:-1

提示:

  • 1 <= nums.length <= 5000
  • -104 <= nums[i] <= 104
  • nums 中的每个值都 独一无二
  • 题目数据保证 nums 在预先未知的某个下标上进行了旋转
  • -104 <= target <= 104
package lanqiaobei;

import java.io.FileNotFoundException;
import java.util.Scanner;

public class Demo01 {  
	public static void main(String[] args) {
		int[] nums = {4,5,6,7,0,1,2};
		int target = 0;
		System.out.println(search(nums,target));
		
	}
	
    public static int search(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        int start = 0;
        int end = nums.length;
        int mid;
        while(start <= end){
            mid = start + (end - start) / 2;
            if(nums[mid] == target){
                return mid;
            }
            //前半部分有序,注意此处用小于等于
            if(nums[start] <= nums[mid]){
                //target在前半部分
                if(target >= nums[start] && target < nums[mid]){
                    end = mid - 1;
                }else{
                    start = mid + 1;
                }
            }else {
                if(target <= nums[end] && target > nums[mid]){
                    start = mid + 1;
                }else{
                    end = mid - 1;
                }
            }
        }
        return -1;
    } 
}

3.搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

示例 1:

img

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:

img

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 100
  • -104 <= matrix[i][j], target <= 104
class Solution {
    public boolean searchMatrix(int[][] mat, int t) {
        int m = mat.length, n = mat[0].length;

        // 第一次二分:定位到所在行(从上往下,找到最后一个满足 mat[x]][0] <= t 的行号)
        int l = 0, r = m - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (mat[mid][0] <= t) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }

        int row = r;
        if (mat[row][0] == t) return true;
        if (mat[row][0] > t) return false;

        // 第二次二分:从所在行中定位到列(从左到右,找到最后一个满足 mat[row][x] <= t 的列号)
        l = 0; r = n - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (mat[row][mid] <= t) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        int col = r;

        return mat[row][col] == t;
    }
}

4.寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转
class Solution {
    public int findMin(int[] nums) {
        int l = 0, r = nums.length - 1, len = r;
        while (l < r) {
            int m = l + r >> 1;
            if (nums[m] > nums[len]) l = m + 1;
            else r = m;
        }
        return nums[l];
    }
}

5.寻找峰值

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

示例 1:

输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。

示例 2:

输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

提示:

  • 1 <= nums.length <= 1000
  • -231 <= nums[i] <= 231 - 1
  • 对于所有有效的 i 都有 nums[i] != nums[i + 1]
class Solution {
    public int findPeakElement(int[] nums) {
        int n = nums.length;
        int l = 0, r = n - 1;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] > nums[mid + 1]) r = mid;
            else l = mid + 1;
        }
        return r;
    }
}

双指针

1.删除排序链表中的重复元素

给定一个已排序的链表的头 head删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表

示例 1:

img

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

示例 2:

img

输入:head = [1,1,1,2,3]
输出:[2,3]

提示:

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }

        //虚拟头节点
        ListNode dummyHead = new ListNode();
        dummyHead.next = head;

        //前置节点
        ListNode pre = dummyHead;
        //当前节点(pre.next==head)
        ListNode cur = pre.next;
        while(pre.next != null){
            //设置计数器计算是否有重复节点
            int count = 0;
            //当前节点的下一个节点不为空,且当前节点的下一个节点值和当前节点值相同
            while(cur.next != null && cur.next.val == cur.val){
                //当前节点就向后移动
                cur = cur.next;
                //计数器增加
                count++;
            }

            //计数器不为0,需要删除前置节点后面的重复的数
            //直接将前置节点的next连接到当前节点的下一个(此时cur是跳出循环后,重复节点的最后一个)
            if(count != 0){
                pre.next = cur.next;
            }else{
                //计数器为0,说明没进入循环,没遇到重复节点
                //前置节点正常向后移动
                pre = cur;
            }
            //不重复时cur正常向后移动
            //重复时,cur是跳出循环后,重复节点的最后一个,因此也需要移动到后一个(也可以合并到if-else里)
            cur = cur.next;
        }
        return dummyHead.next;
    }
}

2.三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> ans = new ArrayList();
        int len = nums.length;
        if(nums == null || len < 3) return ans;
        Arrays.sort(nums);
        for(int i = 0; i < len; i++){
            if(nums[i] > 0) break;//如果当前数组大于>0,则三数之和一定大于0,所以结束循环
            if(i > 0 && nums[i] == nums[i-1]) continue;//去重
            int L = i + 1;
            int R = len -1;
            while(L<R){
                int sum = nums[i] + nums[L] + nums[R];
                if(sum == 0){
                    ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
                    while(L<R && nums[L] == nums[L+1]) L++;//去重
                    while(L<R && nums[R] == nums[R-1]) R--;
                    L++;
                    R--;
                }
                else if(sum < 0) L++;
                else if(sum > 0) R--;
            }
        }
        return ans;
    }
}

BFS实现

//BFS实现
public int maxDepthBFS(TreeNode root){
    //考虑树为空的特殊情况 BFS无法自动处理
    if(root == null){
        return 0;
    }
    //使用队列来记录各层节点
    Queue<TreeNode> queue = new LinkedList<TreeNode>();
    //根节点入队
    queue.offer(root);
    //目标值
    int res = 0;
    //判断是否还有没有遍历完的节点
    while(!queue.isEmpty()){
    	//开始遍历新一层节点前,队列里即为新一层全部节点
        int size = queue.size();
    //需将这一层节点全部遍历完
        while(size > 0){
    		//遍历节点
            TreeNode node = queue.poll();
    		//左子树入队列
            if(root.left != null){
                queue.offer(root.left);
            }
            if(root.right != null){
                queue.offer(root.right);
            }
            //size - 1
        	size--;
        }
          //新一层节点遍历完成 目标值 + 1
    	res++;
    }
    return res;
}

快速排序


public class QuickSort {

    public static int partition(int[] array, int low, int high) {
        // 取最后一个元素作为中心元素
        int pivot = array[high];
        // 定义指向比中心元素大的指针,首先指向第一个元素
        int pointer = low;
        // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
        for (int i = low; i < high; i++) {
            if (array[i] <= pivot) {
                // 将比中心元素小的元素和指针指向的元素交换位置
                // 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
                // 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
                int temp = array[i];
                array[i] = array[pointer];
                array[pointer] = temp;
                pointer++;
            }
            System.out.println(Arrays.toString(array));
        }
        // 将中心元素和指针指向的元素交换位置
        int temp = array[pointer ];
        array[pointer] = array[high];
        array[high] = temp;
        return pointer;
    }

    public static void quickSort(int[] array, int low, int high) {
        if (low < high) {
            // 获取划分子数组的位置
            int position = partition(array, low, high);
            // 左子数组递归调用
            quickSort(array, low, position -1);
            // 右子数组递归调用
            quickSort(array, position + 1, high);
        }
    }

    public static void main(String[] args) {
        int[] array = {6,72,113,11,23};
        quickSort(array, 0, array.length -1);
        System.out.println("排序后的结果");
        System.out.println(Arrays.toString(array));
    }
}

截断数组

//一个数组用两刀切成三段
//先使用前缀和(s[i]=s[i-1]+a[i])确定第二刀的位置
//然后再使用前缀和确定第一刀位置
//最后将两个位置可能的点相加
import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int N = 100010;
        long[] a = new long[N];
        long[] s = new long[N];
        long res = 0,cnt = 0;
        for(int i=1;i<=n;i++){
            a[i]=scan.nextInt();
            s[i]=s[i-1]+a[i];//构造前缀和数组
        }
        if(s[n]%3!=0){//如果元素和不能被3整除,就不满足条件
            System.out.println("0");
            return;
        }
        for(int i=1;i<n;i++){//确定第二刀位置的个数
            if(s[i]==s[n]/3*2) cnt++;
        }
        for(int i=1;i<n;i++){
            if(s[i]==s[n]/3*2) cnt--;
            if(s[i]==s[n]/3) res+=cnt;
        }
        System.out.println(res);
    }
}

并查集

import java.util.Scanner;
public class Main{
    static int N = 100010;
    static int[] p = new int[N];
    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt(); // 全部有n个数
        int m = scan.nextInt(); // 读入m个操作
        //将n个数每个数各自都在一个集合里面。都指向自己,说明现在有多少个集合
        for(int i = 1 ; i <= n ; i ++) p[i] = i; 
        while(m -- > 0){
            String s = scan.next();
            int a = scan.nextInt();
            int b = scan.nextInt();
            //合并集合
            if(s.equals("M")) p[find(a)] = find(b); //将a集合的根节点即祖先指向b集合的祖先 
            else{ //是否同个集合
                if(find(a) == find(b))System.out.println("Yes"); //如果两个集合的祖先相同说明两个集合在同个集合中。
                else System.out.println("No"); //否则相反
            }
        }
    }
    //并查集的核心操作,寻找根节点祖先 + 路径压缩
    public static int find(int x){
        // 如果这个集合的父节点指向的不是自己,说明不是根节点,递归寻找,
        //最后找到根节点之后,把路径上的所有集合都指向根节点、
        if(p[x] != x) p[x] = find(p[x]); 
        return p[x]; // 最后返回根节点
    }
}

笨拙的手指

import java.util.*;
public class finger {
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        char[] a=sc.next().toCharArray();//将输入的字符串转换成字符数组,便于修改字符
        char[] b=sc.next().toCharArray();
        Set<Integer> s=new HashSet<Integer>();//Set集合用于存储转换后的数值,看是否存在交集
        for(int i=0;i<a.length;i++){
            a[i]=(char)((int)a[i]^1);//将该位的0转成1,1转换成0
            s.add(Integer.parseInt(new String(a),2));//使用Integer类的进制转换方法,将某进制转换成10进制【对于数组,可以使用String的构造器将其转换成字符串】
            a[i]=(char)((int)a[i]^1);//复位
        }
        for(int i=0;i<b.length;i++){
            char t=b[i];//由于三进制有三个数,就不能使用异或了,因此采用遍历的方式寻找
            for(int j=0;j<3;j++){
                if(j+'0'!=t){//如果该字符与当前字符不同,就可以试一下该字符
                    b[i]=(char)(j+'0');//将该字符进行更改
                    int x=Integer.parseInt(new String(b),3);//将其转换成10进制
                    if(s.contains(x)){//寻找集合里面是否有该十进制说,如果有就说明该三进制字符串转换一个字符与二进制字符串转换1个字符的十进制相等,输出该数
                        System.out.println(x);
                        return;
                    }
                }
            }
            b[i]=t;//如果该数发生了转换【一定会发生转换的】,但是并没有与二进制转换的十进制相匹配,则进行复位【防止以后转换成10进制时,数值不准确】
        }
    }
}

DFS

public class newText2 {
    public static void main(String[] args) {
        dfs( step );
    }
    static void dfs(int step) {
       
        if (暴搜结束的条件)  //
        {
            System.out.println(输出题目答案);
            return ; //结束
        }
        
        if (x >= n) return;  //判断边界
        
        for ( i=1; i<=n; i++) //尝试每一种可能
        { 
		     dfs(step+1) //继续下一步
		}
    }
}