常见算法思想(java)

最近在刷算法题,在做题的时候也遇到了很多问题,发现好多不会的题其实用的都是一种算法思想,于是打算先整理下常用的算法思想,让自己在做题时思路更加清晰。这段时间也看了很多大佬的博客,最后简单整理了这篇文章,希望对和我一样有疑惑的朋友们有帮助。

狭义的来讲,算法可看作是数据传递和处理的顺序、方法和组成方式,就像是各种排序算法等。而广义的来讲,算法更像是一种事物运行的逻辑和规则。

常见算法思想有:枚举、递推、递归、分治、动态规划、贪心、回溯

一、枚举

也被称为穷尽列举,一般先确定好「可能解」,再进行条件范围筛选,最后验证。

二、递推

递推思想的核心就是从已知条件出发,逐步推算出问题的解。  在代码中通常通过迭代来实现。

案例说明

斐波那契数列(求第n个斐波那契数列的值)

private static long fab_iteration(int n) {
	if (n ==1 ||n == 2) {
		return 1;
	}
	else {
		long f1 = 1l;
		long f2 = 1l;
		long f3 = 0;
		for ( int i = 0; i < n-2; i++) {
			f3 = f1 + f2;//利用变量的原值推算出变量的一个新值
			f1 = f2;
			f2 = f3;
		}
		return f3;
	}
}

三、递归

递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换。

在代码中一般能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。

案例说明

斐波那契数列(求第n个斐波那契数列的值)

private static int fab(int n) {
	if (n == 1 || n == 2) {
		return 1;
	}
	else {
		return fab(n - 1) + fab(n - 2);//递归求值
	}
}

四、分治

分治算法的核心步骤就是两步,一是分,二是治。因此分治的算法思想主要包括两个维度的处理:一是自顶向下,将主要问题划分逐层级划分为子问题;二是自底向上,将子问题的解逐层递增融入主问题的求解中。

分治思想最重要的一点是分解出的子问题是相互独立且结构特征相同的。

案例说明

归并排序就是通过分治的算法思想实现的。

public static void main(String[] args) {
	int arr[] = {6,1,7,2,4,3,8,5};
	//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
	int []temp = new int[arr.length];
	sort(arr,0,arr.length-1,temp);
	System.out.println(Arrays.toString(arr));
}
private static void sort(int[] arr,int left,int right,int []temp){
	if(left<right){
		int mid = left +(right-left)/2;
		sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
		sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
		merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
	}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
	int i = left;//左序列指针
	int j = mid+1;//右序列指针
	int t = 0;//临时数组指针
	while (i<=mid && j<=right){
		if(arr[i]<=arr[j]){
			temp[t++] = arr[i++];
		}else {
			temp[t++] = arr[j++];
		}
	}
	while(i<=mid){//将左边剩余元素填充进temp中
		temp[t++] = arr[i++];
	}
	while(j<=right){//将右序列剩余元素填充进temp中
		temp[t++] = arr[j++];
	}
	t = 0;
	//将temp中的元素全部拷贝到原数组中
	while(left <= right){
		arr[left++] = temp[t++];
	}
}

五、动态规划

动态规划和分治有一点相似,都需要拆分子问题,但与分治的思想不同的是,动态规划常常适用于有重叠子问题和最优子结构性质的问题。

动态规划通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。核心思想:拆分子问题和记住过往,减少重复计算。

动态规划有几个典型特征,最优子结构、状态转移方程、边界、重叠子问题。

1. 案例说明

leetcode的经典题:爬楼梯

一次可以爬1级台阶,也可以爬2级台阶。求爬上 n级的台阶总共有多少种方法。

对于这一题,我们可以分析:

  • 最优子结构:f(n)
  • 状态转移方程:f(n)= f(n-1)+f(n-2)
  • 边界:f(1) = 1, f(2) = 2
  • 重叠子问题:f(4)= f(2)+f(3),f(3) = f(1) + f(2),其中 f(2)就是重叠子问题。
public int climbStairs(int n) {
	if(n<3){
		return n;
	}
	int[] dp = new int[n]; 
	dp[0]=1; dp[1]=2;
	for(int i=2; i<n; i++){
		dp[i] = dp[i-1]+dp[i-2];
	}
	return dp[n-1];
}
2. 动态规划的解题套路

如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划,如最长递增子序列、最小编辑距离、背包问题、凑零钱问题等等。

解题思路:穷举分析\ 确定边界\ 找出规律,确定最优子结构\ 写出状态转移方程。

六、贪心算法

贪心算法在执行的过程中,每一次都会选择最大的收益,但是总收益却不一定最大。

因此贪心算法的一般解题步骤为:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解
案例说明

leetcode问题:分发饼干

给你的孩子们分发饼干,每个孩子有满足胃口的饼干的最小尺寸,要尽可能满足最多数量的孩子。

public int findContentChildren(int[] g, int[] s) {
    int count = 0;
    Arrays.sort(g);
    Arrays.sort(s);
    int j = 0;
    for(int i=0;i<s.length;i++){
        if(j >= g.length){
            break;
        }
        if(s[i]>=g[j]){
            count++;
            j++;
        }
    }
    return count;
}

七、回溯

在做出下一步选择之前,先对每一种可能进行试探;只有当可能性存在时才会向前迈进,倘若所有选择都不可能,那么则向后退回原来的位置,重新选择。通常通过递归来实现。

案例说明

leetcode经典问题:组合总和 II

找出所有可以使数字和为目标值的组合。

class Solution {
    List<List<Integer>> List = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        A(candidates,target,0);
        return List;
    }
    public void A(int[] candidates, int target, int k){
        if(target == 0){
            List.add(new ArrayList<Integer>(list));
            return;
        }
        if( k == candidates.length || target < candidates[k]){
            return;
        }
        int count = 1;
        while(k+count<candidates.length && candidates[k] == candidates[k+count]){
            count++;
        }
        A(candidates,target,k+count);
        for(int i=1; i<=count; i++){
            list.add(candidates[k]);
            A(candidates,target-i*candidates[k],k+count);
        }
        // 每次回溯结束把加进去的数据清除
        for(int i=1; i<=count; i++){
            list.remove(list.size()-1);
        }
    }
}

  • 34
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java常见算法题有很多,以下是一些常见算法题目及其解决方法。 1. 求两个整数的最大公约数和最小公倍数。可以使用辗转相除法来求最大公约数,即不断用较大数除以较小数,直到余数为0,则较小数就是最大公约数。最小公倍数等于两数的乘积除以最大公约数。 2. 数组中找出第K大(小)的数。可以使用快速排序的思想,选取一个基准元素,将数组分为大于基准元素和小于基准元素的两部分,递归地在其中一部分中查找第K大(小)的数。 3. 判断一个字符串是否为回文串。可以使用双指针法,分别从字符串的开头和结尾开始遍历,判断对应字符是否相等,直到两指针相遇或交叉。 4. 实现链表的反转。可以使用迭代或递归的方式,将当前节点的下一个节点指向上一个节点,然后继续遍历链表。 5. 实现二分查找算法。对于有序数组,可以使用二分查找法,在数组的中间位置判断目标值与中间值的大小关系,然后缩小查找范围,直到找到目标值或查找范围为空。 6. 实现图的深度优先搜索(DFS)和广度优先搜索(BFS)。DFS使用递归的方式进行搜索,遍历当前节点的邻接节点,直到遍历完所有节点或找到目标节点。BFS使用队列进行搜索,将当前节点的邻接节点加入队列,并依次出队访问,直到找到目标节点或队列为空。 以上只是一些常见算法题目,掌握这些算法可以帮助我们更好地理解和解决实际问题。当然,还有许多其他的算法题目,不断学习和练习才能更好地掌握。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值