LeeCode刷题简记(六)

本文详细介绍了多种算法在实际问题中的应用,包括二分查找法在寻找数组中目标值区间的问题,双指针技巧在解决有序数组平方和、轮转数组以及字符串反转中的应用,以及位运算是如何用于判断2的幂次方、翻转二进制位和计算位1的个数。这些算法技巧在编程中至关重要,能有效提高代码效率和解决问题的能力。
摘要由CSDN通过智能技术生成

LeeCode算法入门(二分查找、双指针、位运算)

(二分查找)Solution441排列硬币、Solution34在排序数组中相同元素区间、Solution704二分查找、Solution278第一个错误的版本、Solution35搜索插入位置、(双指针)Solution977有序数组的平方、Solution189轮转数组、Solution344反转字符串、Solution876链表的中间节点、Solution557反转字符串中的单词、(位运算)Solution231 2的幂、Solution190颠倒二进制位、Solution191位1的个数

Solution34在排序数组中相同元素区间

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

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

class Solution {
    public int[] searchRange(int[] nums, int target) {
		int index = binarySearch(nums, target); // 二分查找
		
		if (index == -1) { // nums 中不存在 target,直接返回 {-1, -1}
			return new int[] {-1, -1}; // 匿名数组 
		}
		// nums 中存在 targe,则左右滑动指针,来找到符合题意的区间
		int left = index;
		int right = index;
        // 向左滑动,找左边界
		while (left - 1 >= 0 && nums[left - 1] == nums[index]) { 
            // 防止数组越界。逻辑短路,两个条件顺序不能换
			left--;
		}
        // 向左滑动,找右边界
		while (right + 1 < nums.length && nums[right + 1] == nums[index]) { // 防止数组越界。
			right++;
		}
		return new int[] {left, right};
    }
	
	public int binarySearch(int[] nums, int target) {
		int left = 0;
		int right = nums.length - 1; // 不变量:左闭右闭区间
		
		while (left <= right) { // 不变量:左闭右闭区间
			int mid = left + (right - left) / 2;
			if (nums[mid] == target) {
				return mid;
			} else if (nums[mid] < target) {
				left = mid + 1;
			} else {
				right = mid - 1; // 不变量:左闭右闭区间
			}
		}
		return -1; // 不存在
	}
}

Solution441排列硬币

你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。

给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。

迭代算法  缺点时间复杂度较高

class Solution {
    public int arrangeCoins(int n) {
        int i = 1;
        while (n >= i){
            n -= i;
            i++;
        }
        return i - 1;
    }
}

二分  不是很懂

public int arrangeCoins(int n) {
        int left = 0, right = n;
        long all = (long) n;
        while (left <= right) {
            long mid = left + (right - left) / 2;
            if (mid * mid + mid == 2 * all) {
                return (int)mid;
            }
            if (mid * mid + mid > 2 * all) {
                right = (int) mid - 1;
            } else {
                left = (int)(mid + 1);
            }
        }
        return left - 1;
    }

Solution704二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

class Solution {
    public int search(int[] nums, int target) {
        int min = 0;
        int max = nums.length;
        while (min < max){
            int mid = (min + max) / 2;
            if (nums[mid] == target){
                return mid;
            }else if(nums[mid] < target){
                min = mid + 1;
            }else if(nums[mid] > target){
                max = mid;
            }
        }
        return -1;
    }
}

Solution278第一个错误的版本

还是二分查找

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

 不能用(left+right)/2形式,当left和right都是int,两个值的初始值都超过int限定大小的一半,那么left+right就会发生溢出,所以应该用left+(right-left)/2来防止求中值时候的溢出。

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        while(left < right){
            int mid= left + (right-left)/2;
            if(isBadVersion(mid)){
                right=mid;
                }
            else{
                left=mid+1;
                }
        }
        return left;
    }
}

Solution35搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

循环做时,如果整个数组都没有比数大的就返回nums.length做最大数的角标返回

class Solution {
    public int searchInsert(int[] nums, int target) {
        for(int i = 0; i < nums.length;i++){
        if(nums[i] >= target){
            return i;
        }
    }
    return nums.length;
    }
}

二分查找

class Solution {
    public int searchInsert(int[] nums, int target) {
        int n = nums.length;

        // 定义target在左闭右闭的区间,
        int left = 0;
        int right = n - 1;

        while (left <= right) { 
            int mid = left + (right - left) / 2; // 防止溢出
            if (nums[mid] > target) {
                right = mid - 1; // target 在左区间,所以[left, mid - 1]
            } else if (nums[mid] < target) {
                left = mid + 1; // target 在右区间,所以[mid + 1, right]
            } else {
                // 1. 目标值等于数组中某一个元素  return mid;
                return mid;
            }
        }
        // 2.目标值在数组所有元素之前 3.目标值插入数组中 4.目标值在数组所有元素之后 return right + 1;
        return right + 1;
    }
}

Solution977有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

 new一个新数组,指针指向新数组最大数,旧数组两端同时平方,比较后塞到新数组。

class Solution {
    public int[] sortedSquares(int[] nums) {
        int l = 0;
        int r = nums.length - 1;
        int[] res = new int[nums.length];
        int j = nums.length - 1;
        while(l <= r){
            if(nums[l] * nums[l] > nums[r] * nums[r]){
                res[j--] = nums[l] * nums[l++];
            }else{
                res[j--] = nums[r] * nums[r--];
            }
        }
        return res;
    }
}

Solution189轮转数组

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

  1. 反转整个字符串
  2. 反转区间为前k的子串
  3. 反转区间为k到末尾的子串
  4. 如果k大于nums.size就右移 k % nums.size() 次
class Solution {
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n;
        reverse(nums, 0, n - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, n - 1);
    }
    private void reverse(int[] nums, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            int temp = nums[j];
            nums[j] = nums[i];
            nums[i] = temp;
        }
    }
}

Solution344反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

不用库函数,双指针调换位置

class Solution {
    public void reverseString(char[] s) {
        int l = 0;
        int r = s.length - 1;
        while (l < r) {
            char temp = s[l];
            s[l] = s[r];
            s[r] = temp;
            l++;
            r--;
        }
    }
}

Solution876链表的中间节点

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

快慢指针快的走到末尾慢的刚好在中间

public ListNode middleNode(ListNode head) {
        ListNode p = head, q = head;
        while (q != null && q.next != null) {
            q = q.next.next;
            p = p.next;
        }
        return p;
    }

Solution557反转字符串中的单词

给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

 这个不理解,下次回来看

public String reverseWords(String s) {
        //因为字符串不可变,所以必须定义一个可变的字符串来存储新的字符
        StringBuilder ans = new StringBuilder();
        //遍历原字符串,取出单个单词,以空格分开
        for(String str: s.trim().split(" ")){
            //将取出的单词,转化为字符数组的形式
            char[] chars = str.toCharArray();
            //反转单词
            reverseString(chars);
            //将反转后的单词,追加到新的可变字符串中,并加上空格
            ans.append(chars).append(" ");
        }
        //将字符数组转为字符串形式输出,并删除头尾的空格
        //因为在追加最后一个字符的时候,末尾会有一个空格
        return ans.toString().trim();
    }
    public void reverseString(char[] chars){
        //左指针,指向头部
        int left = 0; 
        //右指针,指向尾部
        int right= chars.length-1;
        //只要左指针小于右指针,就交换两个字符
        while(left < right){
            char temp = chars[left];
            chars[left] = chars[right];
            chars[right] = temp;
            //两个指针同时移动
            left++;
            right--;
        }
    }

Solution231 2的幂

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。

  • 重点在于对位运算符的理解
  • 解法1:&运算,同1则1。 return (n > 0) && (n & -n) == n;
  • 解释:2的幂次方在二进制下,只有1位是1,其余全是0。例如:8---00001000。负数的在计算机中二进制表示为补码(原码->正常二进制表示,原码按位取反(0-1,1-0),最后再+1。然后两者进行与操作,得到的肯定是原码中最后一个二进制的1。例如8&(-8)->00001000 & 11111000 得 00001000,即8。 建议自己动手算一下,按照这个流程来一遍,加深印象。
  • 解法2:移位运算:把二进制数进行左右移位。左移1位,扩大2倍;右移1位,缩小2倍。 return (n>0) && (1<<30) % n == 0;
  • 解释:1<<30得到最大的2的整数次幂,对n取模如果等于0,说明n只有因子2。
class Solution {
    /**位运算思路:
     * 2^x = n 因为n 与 n-1 必然为0 比如8(1000) 7(0111)
     * 所以只要判断是否为0即可
     */
    public boolean isPowerOfTwo(int n) {
        if (n < 1){
             return false;
        }
        return (n & n-1) == 0;
    }
}

Solution190颠倒二进制位

颠倒给定的 32 位无符号整数的二进制位。

public class Solution {
    // you need treat n as an unsigned value
    public int reverseBits(int n) {
        int ret = 0;
        int count = 32;
        while (count-- > 0) {
            int lastBit = n & 1; //获取 n 最后一位
            ret = ret << 1 | lastBit; //将结果左移一位后再添加 n 的最后一位
            n = n >> 1; // n 右移来减少一位
        }
        return ret;
    }
}

Solution191位1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称汉明重量)。

public class Solution {
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
        int count = 0;
        while (n != 0) {
            n &= (n - 1);
            count++;
        }
        return count;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值