算法题--哈希(两数之和、给定差值的组合、最小权重路径解法加步骤)

目录

哈希算法思想

题目

两数之和

原题链接

解析

核心思想

答案

给定差值的组合

题目描述

解答要求

解析

核心思想

 答案

最佳路径

题目描述

解答要求

解析

核心思想

递归步骤

 答案


哈希算法思想

利用对象属性作为key值,通过唯一个key值,访问对应的存储的值。相当于形成映射关系,便于直接查找值。

例如一个数组有a、b、c三数,我们可以借用一个对象obj,让obj["a"]=1、obj["b"]=1、obj["c"]=1,当我们需要查找该数组中是否包含d的时候就不需要遍历每个数,只需要判断obj["d"]是否等于1即可。

题目

两数之和

给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列。

(注:返回的数组下标从1开始算起,保证target一定可以由数组里面2个数字相加得到)

输入:[3,2,4],6

返回值:[2,3]

说明:因为 2+4=6 ,而 2的下标为2 , 4的下标为3 ,又因为 下标2 < 下标3 ,所以返回[2,3]

输入:[20,70,110,150],90

返回值:[1,2]

说明:20+70=90

原题链接

两数之和_牛客题霸_牛客网

解析

核心思想

由于需要判断两数相加等于目标值的,所以可以采用减法,用目标值减去当前数对数组的每一位判断是否满足条件,得出下面注释的方法。

通过hash算法优化,将目标值减去当前数作为属性,当前数的序号作为值,当数组中有对应属性的值时即满足相加为目标值,返回序号。

答案

// 不使用hash算法,直接穷举
// function twoSum(numbers, target) {
// 	let tmp = -1
// 	for (let i = 0; i < numbers.length; i++) {
// 		tmp = numbers.findIndex((v, index) => v === target - numbers[i] && i !== index)
// 		if (tmp !== -1) {
// 			return [i + 1, tmp + 1]
// 		}
// 	}
// }
function twoSum(numbers, target) {
	let map = {}
	for (let i = 0; i < numbers.length; i++) {
		if (numbers[i] in map) {
			return [map[numbers[i]], i + 1];
		} else {
			map[target - numbers[i]] = i + 1;
		}
	}
}

给定差值的组合

hash算法

题目描述

给定一个数组,每个元素的值时唯一的,找出其中两个元素相减等于给定差值diff的所有不同组合的个数。

组合是无序的,如:(1,4)和(4,1)表示的是同一个组合。

解答要求

时间限制:1000ms,内存限制:256MB

输入

输入三行:

第一行为一个整数,表示给定差值diff;范围[-50000,50000]

第二行也为一个数字,表示数组的长度;范围[2,102400]

第三行为该数组,由单个空格分割的一组数字组成;其中元素的值范围[-20,102400]

用例保证第三行数字和空格组成的字符串长度不超过64999

输出

1个整数,表示满足要求的不同组合的个数

样例

输入样例1

3

5

1 3 2 5 4

输出样例1

2

提示样例1

数组为[1 3 2 5 4],差值diff为3,其中4-1=3,5-2=3,共2个组合满足要求,因此输出2

输入样例2

-1

3

1 2 3

输出样例2

2

提示样例2

其中1-2=-1,2-3=-1,共2个组合满足要求,因此输出2。

解析

注意方法一中时间复杂度为n²,因为forEach中判断了一轮,includes中对数组同样也判断了一轮。而方法二直接用哈希算法,通过映射的值去查找,去掉了第二轮的判断,时间复杂度为n。

核心思想

方法一:先把数组从大到小排序,然后利用forEach对数组的每一位进行判断,减去绝对值diff后用includes函数判断是否存在于原数组中。

方法二:利用对象的属性,每次添加一个数,使hash对象的属性和值与数相同,直接通过对象的属性名(该数减去diff或加上diff)对应的值是否为undefined来判断差值结果是否存在于原数组中。

 答案

//不使用哈希排序的方法,输入数组过长时,会超时。
// const proc = (arr, diff) => {
//     let sum = 0
//     arr.sort((a, b) => b - a)
//     diff = Math.abs(diff)
//     arr.forEach((v1, i1) => {
//         if (arr.length > i1 + 2) {
//             if (arr.includes(v1 - diff, i1 + 1)) {
//                 sum++
//             }
//         }
//     })
//     return sum
// }
const proc = (arr, diff) => {
    if (diff === 0) {
        return 0
    }
    let sum = 0
    let hash = {}
    for (let i = 0; i < arr.length; i++) {
        const num1 = arr[i];
        hash[num1] = num1;
        if (hash[num1 - diff] != undefined) {
            sum++
        }
        if (hash[num1 + diff] != undefined) {
            sum++
        }
    }
    return sum
}
console.log(proc([1, 3, 2, 5, 4], 3))

最佳路径

hash算法、递归、DFS(深度优先算法)

题目描述

给定单板间的有向连接关系:如单板101到单板102存在连接,权值为10,其连接关系描述为101 102 10;单板102到单板101存在连接,权值为6,其连接关系描述为:102 101 6.

基于单板间的有向连接关系,从起始单板到目的单板可能有0或多条可达的有向路径,某条路径权值是该路径上各连接的权值之和。

给定夺组格式为起始单板目的单板的查询命令,请依次计算并返回其最佳路径权值;若某条最佳路径不存在,则该条最佳路径权值返回-1

最佳路径的规则如下:

经过的连接个数最小;

若符合规则1的路径存在多条,选择权值最小的;

解答要求

时间限制:1000ms,内存限制:256MB

输入

首行为两个正整数n m

n是单板间连接关系的数量,取值范围:[1,500]

m是查询命令的个数,取值范围:[1,500]

接下来n行,每行是连接关系,单板编号取值范围:[1,999],权值取值范围:[1,100];都为正整数

接下来m行,每行是需要查询最佳路径权值的起始单板、目的单板的编号。

输出

m行,每行一个整数,表示对应起始单板、目的单板的最佳路径权值的查询结果

样例

输入样例1

2 2

100 101 10 

102 101 5

100 101

102 100

输出样例1

10

-1

提示样例1

输出第一行:单板100到单板101,最小经过1次跳转就可达,其权值为10,输出10输出第二行:单板102到单板100,没有路径可达,因此输出-1

解析

1.注意这里用到es6中set集合进行判断是否为走过的路径,然后由于是深度优先,且每次调用后都会删除添加的路径,所以不用去浅复制。

2.注意当大于已知解法经过的线段步数时可以不用继续走了。

核心思想

每次走一步,将以下一步为起点的线段存入数组中递归走多步

递归步骤

1.根据递归思想首找到f(x)表示什么,f(x)要表示当前位置,目标位置,经过线段次数,权重和、走过的位置(为避免重复)。

2.找到f(x)和f(x-1)的联系, f(source, target, count, sum, set),其中source根据走的线段更新,目标位置不变,count每次加一,sum加上走的线段权重,set中添加走过线段的终点。f(x)到f(x-1)根据以source为起点的线段条数走多步。

3.找出口,当走的步数大于已知解法的经过线段次数时返回,小于时,source和target相等即找到了正确路径。

 答案

function dfs(source, target, count, sum, set) {
    //countGlobal记录了已有成功的经过的线段次数,如果大于就不进行运算了,减少多余的次数
    if (countGlobal >= count) {
        //出口,当起点为终点时即找到了
        if (source == target) {
            //如果经过有向线段小于已有解法,则直接赋值
            if (count < countGlobal) {
                countGlobal = count
                sumGlobal = sum
            }
            //如果等于,则比较权值weight
            if (count === countGlobal) {
                sumGlobal = Math.min(sumGlobal, sum)
            }
            return
        }
        //没有以当前source为起点的线段了,即走到终点了
        if (!pathMap[source]) {
            return
        }
        const nextList = pathMap[source]
        //以source为起点的线段有多条则需要每条都走一遍
        for (let i = 0; i < nextList.length; i++) {
            const { snkBoard, weight } = nextList[i]
            //判断路径是否走过了
            if (!set.has(snkBoard)) {
                //添加当前走的路径
                set.add(snkBoard)
                dfs(snkBoard, target, count + 1, sum + weight, set)
                //删除之前走的路径,用于下次循环
                set.delete(snkBoard)
            }
        }
    }
}
const pathMap = {}
let countGlobal = 0
let sumGlobal = 0
const getBestRoute = (boardsList, connnectionsList) => {
    //利用hash算法来搜索指定起点的路径
    for (let con of connnectionsList) {
        if (!pathMap[con.srcBoard]) {
            pathMap[con.srcBoard] = []
        }
        pathMap[con.srcBoard].push({ snkBoard: con.snkBoard, weight: con.weight })
    }
    const res = []
    //对每个指定的路径进行计算
    for (let pair of boardsList) {
        let { srcBoard: source, snkBoard: target } = pair
        //记录经过的有向线段个数
        countGlobal = Number.MAX_VALUE
        //记录最小的weght总量
        sumGlobal = Number.MAX_VALUE
        //记录经过了的点,避免重复走
        const set = new Set()
        set.add(source)
        dfs(source, target, 0, 0, set)
        if (sumGlobal === Number.MAX_VALUE) {
            res.push(-1)
        } else {
            res.push(sumGlobal)
        }
    }
    return res
}
console.log(getBestRoute(
    [{ srcBoard: 100, snkBoard: 101 }, { srcBoard: 102, snkBoard: 100 }],
    [{ srcBoard: 100, snkBoard: 101, weight: 10 }, { srcBoard: 102, snkBoard: 101, weight: 5 }])
)

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值