如何高效解决使用哈希表解决双重循环问题

1.双重循环

1.1 什么是双重循环

 简单来说:一个循环体内包含另一个完整的循环结构。

例如说:1.while(循环条件1){
//循环操作1
while(循环条件2){
//循环操作2
}
}
2.do{
//循环操作1
do{
//循环操作2
}while(循环条件2);
}while(循环条件1);
3.for(循环条件1){
//循环操作1
for(循环条件2){
//循环操作2
}
}
4.while(循环条件1){
//循环操作1
for(循环条件2){
//循环操作2
}
}

如上,我们可以很容易发现内部循环变量会随之外部循环变量的改变随之改变。

我们举一个例子说明

例1、给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

代码如下

class Solution {

    public int[] twoSum(int[] nums, int target) {

        int[] indexs = new int[2];

     for(int i = 0; i < nums.length; i++){

             for(int j = nums.length - 1; j > i; j --){

                  if(nums[i]+nums[j] == target){

                   indexs[0] = i;

                   indexs[1] = j; 

                    return indexs;

                }

            }

         }

        return indexs;

    }

}

我们很容易看出内部循环变量j的取值范围是由外部循环变量i决定的。

但要注意两点

1、continue:结束当前循环,进行下次循环。
2、break:结束循环,如果有两重循环,在内层循环总加入break,程序光跳出的是内层循环,外层循环继续执行。

例如

public class Test {

    public static void main(String[] args) {

        for(int i=0; i<10; i++){

            if(i==5){

                break;

            }

            System.out.print(i+" ");

        }

    }

}

public class Test {

    public static void main(String[] args) {

        for(int i=0; i<10; i++){

            if(i==5){

                continue;

            }

            System.out.print(i+" ");

        }

    }

}

这结果我们都知道在循环到i=5的时候,直接跳出外部循环。

break在循环条件下的作用是循环体结构满足时,直接跳出所在循环。

continue在循环条件下的作用是循环体结构满足时,跳出当前循环。

1.2  什么是哈希表

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

记录的存储位置=f(关键字)

这里的对应关系f称为散列函数,又称为哈希(Hash函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。

哈希表hashtable(key,value) 就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。(或者:把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。)
    而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位。

数组的特点是:寻址容易,插入和删除困难;

而链表的特点是:寻址困难,插入和删除容易。

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为“链表的数组”,如图:
左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。

1.3 什么是哈希值(需提前了解散列函数)

散列函数

(或散列算法,又称哈希函数,英语:Hash Function)是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash values,hash codes,hash sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。

重点是唯一性

哈希码

在Java中,哈希码代表了对象的一种特征,例如我们判断某两个字符串是否==,如果其哈希码相等,则这两个字符串是相等的。其次,哈希码是一种数据结构的算法。常见的哈希码的算法有:

1:Object类的hashCode.返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。

2:String类的hashCode.根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串内容相同,返回的哈希码也相同。
3:Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值,例如Integer i1=new Integer(100),i1.hashCode的值就是100 。由此可见,2个一样大小的Integer对象,返回的哈希码也一样。

1.4 如何高效的使用哈希表解决双重循环数组问题

根据上述例1进行解释

使用哈希表解答例1带代码如下

class Solution {

    public int[] twoSum(int[] nums, int target) {

        int[] indexs = new int[2];

        // 建立k-v ,一一对应的哈希表

        HashMap<Integer,Integer> hash = new HashMap<Integer,Integer>();

        for(int i = 0; i < nums.length; i++){

            if(hash.containsKey(nums[i])){

                indexs[0] = i;

                indexs[1] = hash.get(nums[i]);

                return indexs;

            }

            hash.put(target-nums[i],i);

        }

        return indexs;

    }

}

两者对比我们很容易发现,有双重循环解答的时间复杂度有一定的极限

双重循环 循环极限为(n^2-n)/2 

而使用哈希表得出的时间复杂度有一定的缩小

 循环极限为n。

猜你喜欢

转载自blog.csdn.net/xiao_cm/article/details/106317514