您的位置:首页 > 职场人生

面试总结之-查找算法分析

2013-07-12 04:01 357 查看
二分法

static int BinarySearch(int[] a,int tar){ //n是最后一个下标,tar是要找的数
int i = 0, j = a.length-1;
while(i<=j){
int mid = i + (j-i)/2;
if(a[mid] == tar) return mid;
if(a[mid] > tar) j = mid-1;
else i = mid+1;
}
return -1;
}


上面就是一个典型的二分法. 核心思想是: 不断的化小值所在的范围。

二分法有几个需要关注的点:

1. mid = i + (j-i)/2. 防止越界.

2. return i, or j, or mid.

3. a[mid] < tar or a[mid] <= tar.

下面就以上问题进行详细的解释. 简单的二分法,根据以上的变化,可以变化出不同奇特的效果.

例子 : 找大于等于target的最小下标。

public static void main(String[] args) {

// TODO Auto-generated method stub

int a[] = {1,2,3,3,3,3,4,5};

System.out.println(BinarySearch(a,3));

static int BinarySearch_findfristless(int[] a,int tar){
int i = 0, j = a.length-1;
while(i<=j){
int mid = i + (j-i)/2;
if(tar<=a[mid]) //等于往左走
j = mid-1;
else i = mid+1;
System.out.println("mid = " + mid+ "i = " + i+ "j=" + j);
}
System.out.println("i = " +i + "j=" + j);
return i;  //返回i(i比j大)
}

//output
mid = 3i = 0j=2
mid = 1i = 2j=2
mid = 2i = 2j=1
i = 2j=1
2


算法思想: 只要是大于或等于tar. 范围就缩小. [.....]xyz x>=tar. 最后i和j一定会有equal的时候,mid == i. i=mid+1. i 刚好是第一个大于等于tar的值.

所以 return i; 在详细点说 1,2,3,3,3,3,4,5,6,7 aid[mid]<=tar then j = mid -1. 最后 j 一定是在2出现. 类比,如果 aid[mid] < tar. j 一定最后在最后一个3出现

找到最后一个3的位置.

public static void main(String[] args) {

// TODO Auto-generated method stub

int a[] = {1,2,3,3,3,3,4,5};

System.out.println(BinarySearch(a,3));

static int BinarySearch_findfristless(int[] a,int tar){
int i = 0, j = a.length-1;
while(i<=j){
int mid = i + (j-i)/2;
if(tar<a[mid]) //等于往左走
j = mid-1;
else i = mid+1;
System.out.println("mid = " + mid+ "i = " + i+ "j=" + j);
}
System.out.println("i = " +i + "j=" + j);
return j;  //返回i(i比j大)
}

//out
mid = 3 i = 4 j=7
mid = 5 i = 6 j=7
mid = 6 i = 6 j=5
i = 6  j=5
5


例子:题目:在数组中,数字减去它右边的数字得到一个数对之差。求所有数对之差的最大值。例如在数组{2, 4, 1, 16, 7, 5, 11, 9}中,数对之差的最大值是11,是16减去5的结果。

分析:
于是我们接下来可以想到让每一个数字逐个减去它右边的所有数字,并通过比较得到数对之差的最大值。由于每个数字需要和它后面的O(n)个数字作减法,因此总的时间复杂度是O(n2)。
用二分法优化:通常蛮力法不会是最好的解法,我们想办法减少减法的次数。假设我们把数组分成两个子数组,我们其实没有必要拿左边的子数组中较小的数字去和右边的子数组中较大的数字作减法。


数对之差的最大值只有可能是下面三种情况之一:(1)被减数和减数都在第一个子数组中,即第一个子数组中的数对之差的最大值;(2)被减数和减数都在第二个子数组中,即第二个子数组中数对之差的最大值;(3)被减数在第一个子数组中,是第一个子数组的最大值。减数在第二个子数组中,是第二个子数组的最小值。这三个差值的最大者就是整个数组中数对之差的最大值。


(3)被减数在第一个子数组中,是第一个子数组的最大值。减数在第二个子数组中,是第二个子数组的最小值。这个是难点所在:
解决的办法是:记录左数组的最大值和右数组的最小值
如何解决呢?  这个地方就很精彩了! 因为用到了递归,可以用一个引用来记录跟踪Max和min.


int max, min;
return MaxDiffCore(numbers, numbers + length - 1, &max, &min);


public final class test {
static int MaxDiffCore(int[] numbers,int start, int end, int[] a)
{
if(end == start)
{
a[0] = a[1] = numbers[start];
return 0x80000000;
}

int middle = start + (end - start) / 2;

int[] L= {Integer.MAX_VALUE,Integer.MIN_VALUE};
int leftDiff = MaxDiffCore(numbers,start, middle,L);

int[] R= {Integer.MAX_VALUE,Integer.MIN_VALUE};;
int rightDiff = MaxDiffCore(numbers,middle + 1, end, R);

//int crossDiff = maxLeft - minRight;
int crossDiff = L[0] - R[1];

a[0] = (L[0] > R[0]) ? L[0] : R[0];
a[1] = (L[1] < R[1]) ? L[1] : R[1];

int maxDiff = (leftDiff > rightDiff) ? leftDiff : rightDiff;
maxDiff = (maxDiff > crossDiff) ? maxDiff : crossDiff;
return maxDiff;
}

public static int MaxDiff_Solution1(int numbers[])
{
int length = numbers.length;
if(numbers == null || length < 2)
return 0;

int[] a = {Integer.MAX_VALUE,Integer.MIN_VALUE};
return MaxDiffCore(numbers,0,length-1,a);
}

public static void main(String[] args) {
int[] input = {2, 4, 1, 16, 7, 5, 11, 9};
System.out.print(MaxDiff_Solution1(input));
//for(int i : input)
//System.out.print(i+" ");
}

}


这也属于递归总结中的 先递归再处理. 但时间复杂度都是O(n)(第一种解法的时间复杂度可以用递归公式表示为v,所以总体时间复杂度是O(n))。

如果是 T(n)=2(n/2)+O(n) 就是nlogn

这道题也是一个经典的动态规划题目

既然我们可以把求最大的数对之差转换成求子数组的最大和,而子数组的最大和可以通过动态规划求解,那我们是不是可以通过动态规划直接求解呢?下面我们试着用动态规划法直接求数对之差的最大值。

我们定义diff[i]是以数组中第i个数字为减数的所有数对之差的最大值。也就是说对于任意h(h < i),diff[i]≥number[h]-number[i]。diff[i](0≤i<n)的最大值就是整个数组最大的数对之差。

假设我们已经求得了diff[i],我们该怎么求得diff[i+1]呢?对于diff[i],肯定存在一个h(h < i),满足number[h]减去number[i]之差是最大的,也就是number[h]应该是number[i]之前的所有数字的最大值。当我们求diff[i+1]的时候,我们需要找到第i+1个数字之前的最大值。第i+1个数字之前的最大值有两种可能:这个最大值可能是第i个数字之前的最大值,也有可能这个最大值就是第i个数字。第i+1个数字之前的最大值肯定是这两者的较大者。我们只要拿第i+1个数字之前的最大值减去number[i+1],就得到了diff[i+1]。
分析比动手去写要重要!
dynamic programming


int MaxDiff_Solution3(int numbers[], unsigned length)
{
if(numbers == NULL || length < 2)
return 0;

int max = numbers[0];
int maxDiff =  max - numbers[1];

for(int i = 2; i < length; ++i)
{
if(numbers[i - 1] > max)
max = numbers[i - 1];

int currentDiff = max - numbers[i];
if(currentDiff > maxDiff)
maxDiff = currentDiff;
}

return maxDiff;
}


平衡树:

不得不先说一下 Red-Black tree. http://en.wikipedia.org/wiki/Red%E2%80%93black_tree
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

性质1. 节点是红色或黑色。

性质2. 根是黑色。

性质3. 所有叶子都是黑色(叶子是NIL节点)。

性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。

很多牛逼的数据结构都是red-black 实现的, 下面,让我们来总结一下Set.

Set a. HashSet. b. LinkedHashSet. c. TreeSet(red-black 实现)

HashMAp:

一般用来解决 又想跟踪value和index的问题.

two sum :

public class Solution {
public int[] twoSum(int[] numbers, int target) {
// Start typing your Java solution below
// DO NOT write main() function
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
int[] ret = new int[2];
int length = numbers.length;
for(int i =0; i<length; i++)
{
int num = target - numbers[i];

if(map.containsKey(num))
{
ret[0] = map.get(num)+1;
ret[1] = i+1;
return ret;
}else
{
map.put(numbers[i], i);
}
}
return ret;
}
}


3sum 从数组中找3个元素和为target;

无论有没有负数,都有时空复杂度为O(n^2)+O(1)的解法。思路是排序,然后用三个指针(3sum)i,j,k扫描数组,i指针从0~n-3,对于每个i,j都初始化为i+1,k都初始化为n-1,j和k相向的逼近对方,每次判断sum[i]+sum[j]+sum[k]跟target的大小关系,如果sum==target,那么解出来了;如果sum<target,那么j++(因为对于更小的k,sum肯定只会更小,测试下去已经没有意义了);如果sum>target,同理,k--。这样就可以保证检测过了所有可能的解。


class Solution {
public:
vector<vector<int> > threeSum(vector<int> &num) {
sort(num.begin(),num.end());
vector<vector<int>> result;
for(int i=0;i<(int)num.size()-2;i++){
int j = i+1, k=(int)num.size()-1;
while(j<k){
int sum = num[i]+num[j]+num[k];
if(sum==0){
result.push_back(vector<int>());
result.back().push_back(num[i]);
result.back().push_back(num[j]);
result.back().push_back(num[k]);
}
if(sum<=0){
j++;
while(j<k&&num[j-1]==num[j])
j++;
}else{
k--;
while(k>j&&num[k+1]==num[k])
k--;
}
}
while(i<(int)num.size()-2&&num[i+1]==num[i])
i++;
}
return result;
}
};


3 sum closest

class Solution {
public:
int threeSumClosest(vector<int> &num, int target) {
sort(num.begin(),num.end());
int closest = num[0]+num[1]+num[2];
for(int i=0;i<num.size()-2;i++){
int j=i+1,k=num.size()-1;
while(j<k){
int sum = num[i]+num[j]+num[k];
if(abs(closest-target)>abs(sum-target))
closest = sum;
if(sum>=target)
k--;
else j++;
}
}
return closest;
}
};


LinkedHashMap

由于题目与字符出现的次数相关,我们是不是可以统计每个字符在该字符串中出现的次数?要达到这个目的,我们需要一个数据容器来存放每个字符的出现次数。在这个数据容器中可以根据字符来查找它出现的次数,也就是说这个容器的作用是把一个字符映射成一个数字。在常用的数据容器中,哈希表正是这个用途。

例题:

题目:在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。

分析:这道题是2006 年google 的一道笔试题。


public static String findOne(String s)
{
LinkedHashMap<String,Integer> map= new LinkedHashMap<String,Integer>();
for(int i=0; i < s.length();i++)
{
String c = Character.toString(s.charAt(i));
if(map.containsKey(c))
{
int value = map.get(c)+1;
map.put(c, value);
}else
{
map.put(c, 1);
}
}
for(Map.Entry<String, Integer>entry : map.entrySet())
{
if(entry.getValue()==1)
return entry.getKey();
}
return (String) null;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: