场景概述
有N组人,人数分别为a 0 , a 1 , a 2 , . . . , a N − 1 a_0,a_1, a_2, ..., a_{N-1} a 0 , a 1 , a 2 , . . . , a N − 1 ,编号为0 , 1 , 2 , . . . , a 0 − 1 0,1,2,...,a_0-1 0 , 1 , 2 , . . . , a 0 − 1 的人在第0组,其组内编号与编号一致,编号为a 0 , a 0 + 1 , . . . , a 0 + a 1 − 1 a_0,a_0+1,...,a_0+a_1-1 a 0 , a 0 + 1 , . . . , a 0 + a 1 − 1 的人在第1组,其组内编号为0 , 1 , . . . , a 1 − 1 0,1,...,a_1-1 0 , 1 , . . . , a 1 − 1 ,编号为a 0 + a 1 , a 0 + a 1 + 1 , a 0 + a 1 + 2 , . . . , a 0 + a 1 + a 2 − 1 a_0+a_1,a_0+a_1+1,a_0+a_1+2,...,a_0+a_1+a_2-1 a 0 + a 1 , a 0 + a 1 + 1 , a 0 + a 1 + 2 , . . . , a 0 + a 1 + a 2 − 1 的人在第2组。…以此类推。
问:编号为i的人所在组号x为多少?
算法分析
由题意可设
S i = a 0 + a 1 + . . . + a i S_i=a_0+a_1+...+a_i S i = a 0 + a 1 + . . . + a i
则有
第0组的人编号范围为
0 ≤ i < S 0 0 \leq i < S_0 0 ≤ i < S 0
第1组的人编号范围为
S 0 ≤ i < S 1 S_0 \leq i < S_1 S 0 ≤ i < S 1
第2组的人编号范围为
S 1 ≤ i < S 2 S_1 \leq i <S_2 S 1 ≤ i < S 2
则第x组的人编号范围为
S x − 1 ≤ i < S x S_{x-1}\leq i <S_x S x − 1 ≤ i < S x
即我们可以先求出数组S
S 0 , S 1 , S 2 , . . . , S N − 1 S_0, S_1, S_2,..., S_{N-1} S 0 , S 1 , S 2 , . . . , S N − 1
通过二分查找查出比编号i大的最小值S x S_x S x ,则x为其所在组号。i − S x − 1 i-S_{x-1} i − S x − 1 为其组内编号,当x=0时,i本身即为其组内编号。
代码结构
为了使得二分查找算法的通用性,我们可抽象出可随机访问的接口定义如下,并对整形切片实现该接口,
RandomAccessible接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package binarytype RandomAccessible interface { Get(id int ) int Len() int } type SliceAccessible struct { src []int } func (s *SliceAccessible) Get(id int ) int { return s.src[id] } func (s *SliceAccessible) Len() int { return len (s.src) }
SliceAccessible结构体方法测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package binaryimport ( "github.com/stretchr/testify/assert" "testing" ) func TestSliceAccessible_Get (t *testing.T) { sa := SliceAccessible{[]int {1 , 2 , 3 , 4 }} assert.Equal(t, 1 , sa.Get(0 )) assert.Equal(t, 4 , sa.Get(3 )) } func TestSliceAccessible_Len (t *testing.T) { sa := SliceAccessible{[]int {1 , 2 , 3 , 4 }} assert.Equal(t, 4 , sa.Len()) sa = SliceAccessible{[]int {}} assert.Equal(t, 0 , sa.Len()) }
FindFirstBigger函数
1 2 3 4 func FindFirstBigger (data RandomAccessible, target int ) { }
遵循TDD的开发原则,该函数暂时TODO,先编写测试代码
FindFirstBigger函数测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package binaryimport ( "github.com/stretchr/testify/assert" "testing" ) func TestFindFirstBigger (t *testing.T) { s1 := []int {1 , 6 , 21 , 90 , 100 } assert.Equal(t, 0 , FindFirstBigger(&SliceAccessible{s1}, -1 )) assert.Equal(t, 0 , FindFirstBigger(&SliceAccessible{s1}, 0 )) assert.Equal(t, 1 , FindFirstBigger(&SliceAccessible{s1}, 1 )) assert.Equal(t, 1 , FindFirstBigger(&SliceAccessible{s1}, 4 )) assert.Equal(t, 3 , FindFirstBigger(&SliceAccessible{s1}, 21 )) assert.Equal(t, 4 , FindFirstBigger(&SliceAccessible{s1}, 91 )) assert.Equal(t, -1 , FindFirstBigger(&SliceAccessible{s1}, 100 )) assert.Equal(t, -1 , FindFirstBigger(&SliceAccessible{s1}, 104 )) assert.Equal(t, -1 , FindFirstBigger(&SliceAccessible{[]int {}}, 1 )) assert.Equal(t, -1 , FindFirstBigger(&SliceAccessible{[]int {}}, 4 )) s2 := []uint32 {1 , 3 , 6 } as2 := &SliceAccessible[uint32 ]{s2} assert.Equal(t, 1 , FindFirstBigger[uint32 ](as2, 1 )) }
算法代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package binaryfunc FindFirstBigger (data RandomAccessible, target int ) (id int ) { if data.Len() == 0 { return -1 } left := 0 right := data.Len() - 1 for left < right { mid := (right-left)/2 + left if data.Get(mid) > target { right = mid - 1 } else { left = mid + 1 } } if data.Get(left) <= target { left++ } if left >= data.Len() { return -1 } return left
之后便可直接运行单元测试文件对该算法进行测试