目录

​​​​​​​​​​​​​​

并查集

快速排序

字典序算法

二分搜索

开根号-牛顿开方法

求质数

编辑距离

滑动窗口

异或求重

长除法


​​​​​​​

并查集

并查集用于解决相同元素集合动态连接的快速构建算法

例:求相等集合a=b,e=d,d=b

初始时,ab为一集合,ed为一集合,又d=b,故应将abed变为一集合,之后e=a就是显而易见的事了

并查集利用构建树形结构的方式,每个元素有上级,上级有上级,最高级的上级为其本身,通过查看两个元素的最高级是否相同来判断两元素是否属于同一集合

即,每个集合为一棵树,判断两个元素是否属于同一个集合只用判断其树根是否相同即可

初始时有数组pre[],pre[i]表示元素i的上一级

find(int x)用于找出元素x的最高级,即树根

  1. int find(int x){
  2. while(x!=pre[x]){
  3. x=pre[x];
  4. }
  5. return x;
  6. }

union(int x,int y)用于将x,y所代表的两个集合合并

  1. void union(int x,int y){
  2. int a=find(x);
  3. int b=find(y);
  4. pre[b]=a;
  5. }

例题(leetcode-990):

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:"a==b" 或 "a!=b"。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false

示例 1:

输入:["a==b","b!=a"]
输出:false
解释:如果我们指定,a = 1 且 b = 1,那么可以满足第一个方程,但无法满足第二个方程。没有办法分配变量同时满足这两个方程。

示例 3:

输入:["a==b","b==c","a==c"]
输出:true

示例 5:

输入:["c==c","b==d","x!=z"]
输出:true

提示:

  1. 1 <= equations.length <= 500
  2. equations[i].length == 4
  3. equations[i][0] 和 equations[i][3] 是小写字母
  4. equations[i][1] 要么是 '=',要么是 '!'
  5. equations[i][2] 是 '='
class Solution {//使用一下传统并查集private  int[] pre=new int[2560];//存储其上一级,最高级为其本身,成为代理人//发现x所在集合的最高代理人private int find(int x){while(x!=pre[x]){x=pre[x];}   return x;}//两者应该属于同一伙,合并,为使其成为树状结构,加快查找速度,将最高代理人合并private void union(int x,int y){int a=find(x);int b=find(y);if(a!=b){pre[b]=a;}}public boolean equationsPossible(String[] equations) {for(int i=0;i<2560;i++){pre[i]=i;//初始化}for(String s:equations){int l=s.charAt(0);int r=s.charAt(3);if(s.charAt(1)=='='&&find(l)!=find(r)){union(l,r);}}for(String s:equations){int l=s.charAt(0);int r=s.charAt(3);if(s.charAt(1)=='!'&&find(l)==find(r)){return false;}}return true;}}

参考:https://blog.csdn.net/liujian20150808/article/details/50848646

快速排序

快速排序以分治法为基本思想,递归为基本表现形式。

对于一个数组nums,传递参数left,right,为范围,以第一个数(nums[0])为key,将所有<=key的数放在左边,所有>key的数放在右边。

i=left,j=right,向前推进i直到找到第一个>key的数,向后推进直到找到第一个<=key的数

交换i,j的数的位置直到i==j

退出循环,此时i==j,核查nums[i]与key,保证<=i的数全部<=key,>j的数全部>j。因为以nums[0]为基准,nums[0]不能在参加递归运算,交换nums[i]与nums[0],继续递归,范围为[left,i-1],[j,right]

递归的结束条件即为left>=right

    private void sort(int[] nums,int left,int right){if(left>=right)return ;int i=left,j=right;int key=nums[left];while(i<j){while(i<j&&nums[j]>key){j--;}while(i<j&&nums[i]<=key){i++;}int tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;}if(nums[i]<=key)j++;else i--;nums[left]=nums[i];nums[i]=key;//基准数不再参与数据处理sort(nums,left,i-1);sort(nums,j,right);}

字典序算法

字典序即按照a-zA-Z0-9的顺序对由这些元素组成的串的从小到大的全排列。

如[1,2,3]进行全排列,将1,2,3组成数字,字典序就相当于按所有组成数字从小到大进行排列。

123->132->213->231->312->321

字典序算法可用于全排列(n!)生成

字典序算法的步骤为:

  • 1.从右至左找到第一个左邻小于右邻,记录左邻位置i
  • 2.从右至左找到第一个大于左邻的数,即位置为j,交换num[i],num[j]
  • 3.对位置i之后的所有数进行排列

例题(leetcode-31)

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

class Solution {public void nextPermutation(int[] nums) {//考察字典序算法,字典序可用于生成全排列//字典序算法步骤为://1.从右至左找到第一个左邻小于右邻,记录左邻位置i//2.从右至左找到第一个大于左邻的数,即位置为j,交换num[i],num[j]//3.对位置i之后的所有数进行排列//如果没找到i则说明已经是最大的排列数int a=-1;//1.从右至左找到第一个左邻小于右邻,记录左邻位置ifor(int i=nums.length-1;i>0;i--){if(nums[i-1]<nums[i]){a=i-1;break;}}if(a==-1){sort(nums,0,nums.length-1);return ;}//2.从右至左找到第一个大于左邻的数,即位置为j,交换num[i],num[j]for(int i=nums.length-1;i>a;i--){if(nums[i]>nums[a]){int temp=nums[i];nums[i]=nums[a];nums[a]=temp;sort(nums,a+1,nums.length-1);return ;}}}private void sort(int[] nums,int left,int right){if(left>=right)return ;int i=left,j=right;int key=nums[left];while(i<j){while(i<j&&nums[j]>key){j--;}while(i<j&&nums[i]<=key){i++;}int tmp=nums[i];nums[i]=nums[j];nums[j]=tmp;}if(nums[i]<=key)j++;else i--;nums[left]=nums[i];nums[i]=key;//基准数不再参与数据处理sort(nums,left,i-1);sort(nums,j,right);}
}

二分搜索

太经典了,看代码即可,nums为已排序数组

        int left=0,right=nums.length-1;while(left<=right){int med=(left+right)/2;if(nums[med]==target)return med;else if(nums[med]<target){left=med+1;}else{right=med-1;}}

开根号-牛顿开方法

实际上就是完成一个sqrt()函数,计算机完成开根号的运算,大部分使用牛顿迭代法。

假设a为要开方的数,那么x就是答案,即求f(x)=0时,x为何值

选取函数上一点(t,t^2-a),对于该点,求切线,切线上在x轴方向上的零点必定逼近于结果

切线的斜率对原函数求导即可,k=2t,该切线经过点(t,t^2-a),求切线在x轴方向上的零点,即(t^2+a)/2t

以该点继续迭代即可不断逼近与结果

    public int mySqrt(int x) {//牛顿迭代法//y=res^2-x,选取点(a,a^2-x)做切点//斜率即求导数k=2a,令y=2ares`+t//带入化简,零点为res`=(a^2+x)/2a,继续迭代if(x==0)return 0;if(x<=3)return 1;long current=x/2;while(current*current>x){current=((current+x/current)/2);}return (int)current;}

当然,原理上是这么算的,实际上想要运行的更快还有一些技巧

关于这个方法,还有一个神奇的数字

0x5f3759df

​雷神之锤

参看:https://www.cnblogs.com/pkuoliver/archive/2010/10/06/1844725.html

求质数

有两种方法

1.暴力求解,对每一个数进行判断,判断到sqrt(n)

for(int i=2;i<=n;i++){for(int j=2;j<=sqrt(i);j++){if(i%j==0)==>i不是质数;break;}}

2.筛选法,对于2来说,2的倍数一定不是质数,对于3来说,3的倍数一定不是质数。。开一个int[n]的数组,对于2,3...n

2是质数,但2的倍数不是,打表,每次先判断当前是否被打上去,如果是直接跳过,否则是质数,打表。

 boolean[] check=new boolean[n+1]int tot = 0;for (int i = 2; i <= n; ++i)
{if (!check[i]){prime[tot++] = i;}for (int j = i+i; j <= n; j += i){check[j] = true;}}

编辑距离

有两个具有相同性质的物质可以互相转换,字符串A转换成字符串B,A,B都由字符构成,给定基本条件,求A能转换成B所需要的最小步骤,这个问题比较抽象,而所用的就是编辑距离算法,实际上还是动态规划算法。

        编辑距离是针对两个字符串(例如英文字)的差异程度的量化量测,量测方式是看至少需要多少次的处理才能将一个字符串变成另一个字符串。编辑距离可以用在自然语言处理中,例如拼写检查可以根据一个拼错的字和其他正确的字的编辑距离,判断哪一个(或哪几个)是比较可能的字。DNA也可以视为用A、C、G和T组成的字符串,因此编辑距离也用在生物信息学中,判断二个DNA的类似程度。(来自百度百科)

例题(leetcode-72)

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符
示例 1:

输入: word1 = "horse", word2 = "ros"
输出: 3
解释: 
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')

假设dp[i][j]表示前i个字符转换成前j个字符所需要的最短距离

对于当前字符word1[i]与word2[j]来说,

如果word[i]==word[j],显然 dp[i][j]=dp[i-1][j-1]

否则,有三种方法可以对当前字符进行处理。

  • 1.在前i个字符前插入一个字符,显然我们不知道插到哪里合适,但是如果插入一个字符与word2[j]相同,那么就至少是最小变换中的一种,因为如果插入到别的地方,当前字符还是和word2[j]不同,还要执行变换操作,而插入字符放在最前面,那么dp[i][j]=1+dp[i][j-1](因为插入字符和word2[j]相同)
  • 2.在前i个字符中删除一个字符,显然,删谁都是可以的,但是因为word1[i]和word2[j]不相同,那么删除word1[i]至少是最小变换中的一种,即dp[i][j]=1+dp[i-1][j]
  • 3.替换一个字符,替换word1[i]=word2[j],则dp[i][j]=1+dp[i-1][j-1]

由以上推断就可以写动归代码,注意底层条件的初始设置。

class Solution {public int minDistance(String word1, String word2) {int[][] dp=new int[word1.length()+1][word2.length()+1];//初始化底层条件for(int i=1;i<=word1.length();i++){dp[i][0]=i;}for(int i=1;i<=word2.length();i++){dp[0][i]=i;}for(int i=0;i<word1.length();i++){for(int j=0;j<word2.length();j++){if(word1.charAt(i)==word2.charAt(j)){dp[i+1][j+1]=dp[i][j];}else{dp[i+1][j+1]=1+Math.min(Math.min(dp[i][j+1],dp[i+1][j]),dp[i][j]);}}}return dp[word1.length()][word2.length()];}
}

滑动窗口

这是一个非常经典的思想,常用于在给定的范围中寻求包含的子范围。

滑动窗口维护两个指针:left,right

最开始left和right都处于最左边,right向右滑动扩大窗口直到子窗口[left...right]已满足条件

这时就要开始动左指针left,对于left当前状态对应的元素,判断是否能将其抛出,如果能left向右滑动,即缩小窗口,同时再次进行判断,如果不能则动右指针right扩大窗口。简而言之,right扩大窗口,left缩小窗口,整个窗口一次遍历时就能遍历到所有符合条件的子窗口。

leetcode官方描述:

  • 初始,left指针和right指针都指向SS的第一个元素.
  • 将 right 指针右移,扩张窗口,直到得到一个可行窗口,亦即包含TT的全部字母的窗口。
  • 得到可行的窗口后,将left指针逐个右移,若得到的窗口依然可行,则更新最小窗口大小。
  • 若窗口不再可行,则跳转至 22。

例题(leetcode-76)

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:

如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

class Solution {public String minWindow(String s, String t) {//暴力法O(n*2*m),超时//使用滑动窗口,left缩减窗口,right扩大窗口,窗口大小可动态变化Map<Character,Integer>t2=new HashMap<>();for(char c:t.toCharArray()){t2.put(c,t2.getOrDefault(c,0)+1);}if(!check(getMap(s),t2))return ""; int left=0;int right=t.length()-1;int ls=0;int rs=s.length()-1;//t1也跟着动态变化Map<Character,Integer>t1=new HashMap<>();for(char c:s.substring(0,t.length()).toCharArray()){t1.put(c,t1.getOrDefault(c,0)+1);}while(right<s.length()){if(check(t1,t2)){//滑动窗口包含t,收缩滑动窗口if(right-left<rs-ls){rs=right;ls=left;}t1.put(s.charAt(left),t1.get(s.charAt(left))-1);left++;continue;}//不包含,扩展滑动窗口right++;if(right<s.length())t1.put(s.charAt(right),t1.getOrDefault(s.charAt(right),0)+1);}return s.substring(ls,rs+1);}private Map<Character,Integer> getMap(String s){Map<Character,Integer>t1=new HashMap<>();for(char c:s.toCharArray()){t1.put(c,t1.getOrDefault(c,0)+1);}return t1;}//t1完全包含t2private boolean check(Map<Character,Integer>t1,Map<Character,Integer>t2){for(char c:t2.keySet()){if(!t1.containsKey(c))return false;if(t1.get(c)<t2.get(c))return false;}return true;}
}

异或求重

异或的性质:

1、交换律:a^b = b^a;

2、结合律:(a^b)^c = a^(b^c);

3、对于任意的a:a^a=0,a^0=a,a^(-1)=~a

由以上性质可以得出一个重要推导:a^b^c^d^a^k = b^c^d^k^(a^a) = b^c^d^k^0 = b^c^d^k,即如果有多个数异或,其中有重复的数,则无论这些重复的数是否相邻,都可以根据异或的性质将其这些重复的数消去。如果有偶数个数则异或后为0,如果为奇数个,则异或后只剩一个。(引用于:https://blog.csdn.net/ns_code/article/details/27568975

1、1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次。每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间,能否设计一个算法实现?

当然,这道题,可以用最直观的方法来做,将所有的数加起来,减去1+2+3+...+1000的和,得到的即是重复的那个数,该方法很容易理解,而且效率很高,也不需要辅助空间,唯一的不足时,如果范围不是1000,而是更大的数字,可能会发生溢出。

我们考虑用异或操作来解决该问题。现在问题是要求重复的那个数字,我们姑且假设该数字式n吧,如果我们能想办法把1-1000中除n以外的数字全部异或两次,而数字n只异或一次,就可以把1-1000中出n以外的所有数字消去,这样就只剩下n了。我们首先把所有的数字异或,记为T,可以得到如下:

T = 1^2^3^4...^n...^n...^1000 = 1^2^3...^1000(结果中不含n)

而后我们再让T与1-1000之间的所有数字(仅包含一个n)异或,便可得到该重复数字n。如下所示:

T^(a^2^3^4...^n...^1000) = T^(T^n) = 0^n = n

2、一个数组中只有一个数字出现了一次,其他的全部出现了两次,求出这个数字。

明白了上面题目的推导过程,这个就很容易了,将数组中所有的元素全部异或,最后出现两次的元素会全部被消去,而最后会得到该只出现一次的数字。

长除法

用于大精度除法求小数

模拟除法中,小数上的商可以重复,或陷入无限循环(小数循环节),而商实际上是由上一次剩下的模数来决定的,商可以重复而模数不能重复,一旦模数开始重复就代表循环节开始出现

例题:leetcoed分数到小数

给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数。

如果小数部分为循环小数,则将循环的部分括在括号内。

示例 1:

输入: numerator = 1, denominator = 2
输出: "0.5"

示例 2:

输入: numerator = 2, denominator = 1
输出: "2"

示例 3:

输入: numerator = 2, denominator = 3
输出: "0.(6)"
class Solution {public String fractionToDecimal(int numerato, int denominato) {//模拟除法,由题意判断不会出现无限不循环小数//***********模拟长除法:(商的)小数循环节出现则每次除下的模数也会重复出现//原理:模数一旦重复则代表要出现循环节if(numerato==0)return "0";char flag='+';if((numerato>0&&denominato<0)||(numerato<0&&denominato>0))flag='-';long numerator=Math.abs((long)numerato);//不用long会溢出long denominator=Math.abs((long)denominato);String res="";//能够整除if(numerator>=denominator&&numerator%denominator==0){return flag=='+'?String.valueOf(numerator/denominator):'-'+String.valueOf(numerator/denominator);}//不能够整除,注意符号Map<Long,Integer>map=new HashMap<>();//记录模数与每次除下的商所在的位置res+=numerator/denominator+".";numerator%=denominator;while(true){//结束条件:能够除尽或者陷入无限循环(模数重复出现)if(map.containsKey(numerator)){res=res.substring(0,map.get(numerator))+"("+res.substring(map.get(numerator))+")";break;}map.put(numerator,res.length());//放模数numerator*=10;res+=numerator/denominator;//System.out.println(numerator+" "+denominator);numerator%=denominator;//模数更新if(numerator==0){break;}}return flag=='+'?res:'-'+res;}}

注意对于int除法来说为保证能求得准确精度应该使用long类型,否则模数一直*10后面可能会溢出

经典算法-并查集、快速排序、字典序算法、二分搜索、牛顿开方法、求质数(筛选法)、编辑距离、滑动窗口、异或求重、长除法相关推荐

  1. 基于并查集的kruskal算法

    #include <iostream> //并查集的kruskal算法using namespace std;const int max_ve=1005,max_ed=15005;int ...

  2. UVA10034 Freckles【Kruskal算法+并查集】

    In an episode of the Dick Van Dyke show, little Richie connects the freckles on his Dad's back to fo ...

  3. 亲戚关系关系算法java程序_C++并查集亲戚(Relations)算法实例

    本文实例讲述了C++并查集亲戚(Relations)算法.分享给大家供大家参考.具体分析如下: 题目: 亲戚(Relations) 或许你并不知道,你的某个朋友是你的亲戚.他可能是你的曾祖父的外公的女 ...

  4. [基础算法] 并查集

    并查集 1.将两个集合合并 2.查询两个集合是否在同一个集合中 基本原理:每个集合用同一棵树来表示,树根的编号就是树的编号,每个节点储存其父节点,p[x]表示x的父节点. 问题一:如何判断是否为根节点 ...

  5. 关于 并查集(union find) 算法基本原理 以及 其 在分布式图场景的应用

    二月的最后一篇水文-想写一些有意思的东西. 文章目录 环检测在图数据结构中的应用 深度/广度优先 检测环 并查集数据结构 (Union-Find) 基本概念 初始化 合并 union 查找祖先 优化1 ...

  6. 普林斯顿算法(1.3)并查集(union-find算法)——本质就是一个数 下面的子树代表了连在一起的点...

    转自:https://libhappy.com/2016/03/algs-1.3/ 假设在互联网中有两台计算机需要互相通信,那么该怎么确定它们之间是否已经连接起来还是需要架设新的线路连接这两台计算机. ...

  7. 最小生成树kruskal算法并查集版 C语言实现

    今天数据结构课讲了最小生成树的Kruskal算法和Prim算法,不过都只是概念,可能是怕他们听不懂吧,反正算法实现一概不讲...囧 下午抱着<算法导论>跑去图书馆看Kruskal算法,发现 ...

  8. Kruscal算法+并查集 求解最小生成树

    http://ac.jobdu.com/problem.php?pid=1347    孤岛连通工程 刚开始的时候使用qsort排序函数进行排序提交一直都是TLE,后来无意中改为sort排序函数提交就 ...

  9. 并查集与贪心算法的应用之求解无向图的最小生成树

    一,介绍 本文介绍使用Kruskal算法求解无向图的最小生成树.Kruskal是一个贪心算法,并且使用了并查集这种数据结构. 关于并查集的介绍,参考:数据结构--并查集的原理及实现 二,构造一个无向图 ...

最新文章

  1. [译]php和curl_multi_exec
  2. 用于精准判断的贝叶斯定理
  3. 同济大学计算机系陈永生,城市轨道交通应急预案演算平台的设计与研究.pdf
  4. GDCM:获取图像像素间距的测试程序
  5. split | notes in java
  6. 查看端口被占用的进程号然后结束进程(解决端口被进程占用的问题)
  7. 计蒜客 贝壳找房函数最值(好题,巧妙排序)
  8. 华为secoclient提示“无法建立vpn链接,vpn服务器可能无法到达”
  9. JAVA打印中文乱码问题
  10. 关于生活,你可以选择-转自网络
  11. linux游戏主机,Steam OS界面介绍
  12. OEM、ODM与OBM的区别
  13. 衡山湘大学计算机学校,南岳衡山烧香求学业显灵感恩南岳大庙祈福考上985双一流重点大学...
  14. NOVA温控器参数笔记(十)(故障代码)
  15. 企业未来的发展机遇,或许在直播中
  16. System.Windows.Freezable 在未被引用的程序集中定义
  17. Python pandas练习Retuns50stocks股票,纯英文ipynb作业20题,100%正确答案
  18. 6种Python反反爬虫技术,看完后我的爬虫技术提升了
  19. 关于auth0的jwt
  20. Android 开发 设置banner圆角,滑动时,图片圆角失效

热门文章

  1. linux 监听终端输入法,xinit 终端中文输入法
  2. R语言ggplot2优雅的绘制环状云雨图
  3. 庄懂老师TA学习笔记 - 半兰伯特光照模型
  4. Android开发在路上:少去踩坑,多走捷径(上)
  5. js正则禁止输中文韩文日文
  6. 七丶人生苦短,我用python【第七篇】
  7. 科研人必备论文小技巧——word分分钟自动修改插入的参考文献、附图和表格的插入技巧
  8. C++语言涉猎笔记(二)
  9. 人民币为什么会贬值?
  10. 字节后端实习技术三面面经