探索数据结构与算法——如何运用异或,二分解题

本文详细介绍了异或操作在解决交换两个整数、寻找数组中只出现一次的元素等问题中的应用,以及如何利用二分法在有序数组中查找指定元素及其变体问题。通过实例解析,帮助读者理解这两种算法思想,并提供了相关练习巩固。
摘要由CSDN通过智能技术生成

目录

📔 前言

📘 异或

📑 例题引入

📜 练习巩固

📒 二分法

📕 例题引入

📰 练习巩固

 📰 总结:


📔 前言

数据结构与算法如今在工作面试中占很高的比重,为了学好算法,大多数人都会去看各种书籍与视频,然后去做题,以做leetcode为例,没学算法前,你可能可以做些简单题,中等题也会一部分,学过数据结构与算法后,你可能发现自己会的中等题变多了,但依然有许多题无从下手,想暴力可时间过不了,此类情况就是无法将所学结合起来,没想到能用学过的方法解题,接下来,我们就来通过题目,剖析异或与二分。

📘 异或

异或作为位运算的一种,其运算方法为:相同(1与1,0与0)为0,相异为1

📑 例题引入

交换两个整数a,b 不能创建其他变量

void swap(int *a,int *b)
{
 a^=b;
 b^=a;
 a^=b;
}

代码很简单,异或就能解决,那么我们先来剖析一下怎么实现的

首先,我们要了解一些知识:异或是满足交换律和结合律的即:

a^b==b^a;
(a^b)^c==a^(b^c);

然后,一个数异或0为这个数本身,异或自生则为0

a^0==a;
a^a==0;

这个本身指的不是数字相同,而是开辟的空间相同,故只要开辟的空间不一样,那么异或后就不会为0.

a^=b ——> a=a^b

b^=a——>b=b^a^b——>b=b^b^a=a

a^=b——>a^b^a——>a^a^b——>=b

以上便是交换的

过程,了解了这些知识,接下来我们便可以做更多类型的题

📜 练习巩固

136. 只出现一次的数字

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

示例 2:

输入: [4,1,2,1,2]
输出: 4

 此题用异或计算非常简单,异或数组,因为交换律二次的异或后都为0,只剩出现一次的数,奇偶也是此方法,如一个数出现了奇数次,其他数出现了偶数次

int singleNumber(int* nums, int numsSize){
    int j=0;
for(int i=0;i<numsSize;i++)
{
 j^=nums[i];
}
return j;
}

260. 只出现一次的数字 III

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

示例 1:

输入:nums = [1,2,1,3,2,5]
输出:[3,5]
解释:[5, 3] 也是有效的答案。

示例 2:

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

示例 3:

输入:nums = [0,1]
输出:[1,0]
/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* singleNumber(int* nums, int numsSize, int* returnSize){
long long num=0;long long num1=0;
int *arr=(int*)malloc(sizeof(int)*2);
for(int i=0;i<numsSize;++i)
{
    num^=nums[i];
}
long long num2=num;
num1=num&(~num+1);
for(int i=0;i<numsSize;++i)
{
    if((num1&nums[i])!=0)
    num^=nums[i];
}
num2^=num;
arr[0]=num;arr[1]=num2;
*returnSize=2;
return arr;
}

 要解决此题当然也是通过异或数组,接下来,我们通过代码来剖析方法:

前半段代码异或数组

for(int i=0;i<numsSize;++i)
{
    num^=nums[i];
}

此时得到的是两个出现一次元素的异或且num肯定大于0,接下来这行便有意思了

long long num2=num;
num1=num&(~num+1);

这行什么意思呢?我们画图理解:

 知道这个可能部分人就能想出来为什么要这么做了,不知道也没关系,我们继续剖析

for(int i=0;i<numsSize;++i)
{
    if((num1&nums[i])!=0)
    num^=nums[i];
}

这段代码其实是在做区分,前边说了,两个出现一次的数异或其结果必不是0,那么,我们便找到其最右边的一(上一步),把整个数组分为两部分:一部分是这位上含一的,一部分是不含一的

📒 二分法

  相信二分法大家早就很熟悉了,学习编程语言的时候我们就学习过二分法,但做题时可能会遗忘这种方法

📕 例题引入

给定一个有序数组,找到其中指定的数,返回其下标

#include<stdio.h>
void erfen(int a[],int b,int sz)
{
int left=0;
int right=sz-1;
while(left<right)
{
int mid=(left+right)/2;
if(b>a[mid])
left=mid+1;
if(b<a[mid])
right=mid-1;
else
{printf(“找到了下标为%d\n”,mid);
break;}

}
if(left>right){
printf(“找不到”);
}
int main()
{
int b=0;
printf(“请输入1~10间的一个数\n”);
scanf("%d",&b);
int a[]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(a)/sizeof(a[0]);
void erfen(a,b,sz);
return 0;
}

 定义两个变量记录每次分的左右端,让其中间值与数进行比较,直到出现指定数或无法再分,时间复杂度为

大家都懂,就不过多赘述了。

📰 练习巩固

1.

给定一个有序数组,如1,1,1,2,2,2,3,3,3,4,4,4,5,5,5

给定一个num,输出大于等于num最左侧的值或者小于等于num最右侧的值的下标。

假如num=3,大于等于最左侧的下标为6,小于等于最右侧的下标为8,可以二分直到二分结束

没找到例题,就用这个吧(这题我们就用最左侧吧)

 

 用一个变量记录打勾的下标(即等于num的坐标,比较最小值进行替换)

 这便是此题的解题思路,当然你也可以暴力求解复杂度O(N),二分法则是O(logN)。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int num;
	int arr[] = { 1,1,1,2,2,2,3,3,3,4,4,4,5,5,5 };
	scanf("%d", &num);
	int left = 0,count=14,mid=0;
	int sz = sizeof(arr);
	int right = sz - 1;
	while (left <= right)
	{
	     mid = (left + right) / 2;
		if (num < arr[mid])
		{
			right = mid - 1;
		}
		if (num > arr[mid])
		{
			left = mid + 1;
		}
		if (num == arr[mid])
		{
			right= mid - 1;
			if (count > mid)
				count = mid;
		}
	}
	printf("%d", count);
}

 2.局部最小

给定无序数组arr,且其中相邻两个数一定不相等,例如0位置上的数小于1位置上的数,那么0位置局部最小,如果n-1位置上的数比n-2位置上的数小,那么n-1就是局部最小,如果i<i-1且i<i+1,那么,i是局部最小。

思路如下:

 

 同样,也是一直二分直到结束

162. 寻找峰值

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

给你一个整数数组 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。

int findPeakElement(int* nums, int numsSize){
int left=0,right=numsSize-1;
    while(left < right)
    {
        int mid = (left + right) / 2;
        if(nums[mid] > nums[mid + 1]) 
        right = mid;  
        else 
        left = mid+1;    
    }
    return right;
}

 📰 总结:

二分法可用于:

1.有序数组找下标

2.有序数组中大于等于一个值最左侧,小于等于这个值的最右侧下标

3.局部最小,局部最大

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shany-Ming

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值