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
是非负数。
- 反转整个字符串
- 反转区间为前k的子串
- 反转区间为k到末尾的子串
- 如果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;
}
}